This article was sponsored by Microsoft. Thank you for supporting the sponsors who make SitePoint possible.
本文由Microsoft赞助。感谢您支持使SitePoint成为可能的赞助商。
Welcome back to our series of articles using Microsoft’s modern IDE: Visual Studio Community to quickly design and build an attractive, functional site for a client. If you missed the previous instalments, check them out below:
欢迎使用Microsoft的现代IDE返回我们的系列文章:Visual Studio Community ,为客户快速设计和构建一个有吸引力的功能站点。 如果错过了以前的付款,请在下面查看:
Visual Studio Community : Setting Up a Site
Visual Studio Community :设置网站
Visual Studio Community : Adding Email and Contact Pages
Visual Studio社区:添加电子邮件和联系页面
In this article, we’re going to add an ecommerce solution to our site. The solution will be simple, consisting of a single page to handle the shopping cart, payment information and success or fail messages for the user.
在本文中,我们将向网站添加电子商务解决方案。 该解决方案将很简单,包括一个用于处理购物车的页面,付款信息以及用户的成功或失败消息。
We’ll submit our payment information to a mock Web API app. The Web API app will act like a credit card processor. It will consume JSON via POST. It will then send back an HTTP status code indicating success or failure. Based on this feedback, we can determine what to do. But we’ll keep it simple by showing the user a message based on the received status code.
我们会将付款信息提交到模拟的 Web API应用程序。 Web API应用程序将像信用卡处理器一样工作。 它将通过POST使用JSON。 然后它将发送回HTTP状态代码,指示成功或失败。 根据此反馈,我们可以确定要做什么。 但是,我们将根据收到的状态码向用户显示一条消息,以使其保持简单。
While the solution is simple, it will demonstrate core components of any ecommerce solution. These include:
虽然该解决方案很简单,但是它将演示任何电子商务解决方案的核心组件。 这些包括:
Listing of products to buy购买产品清单 Ability to add or remove products能够添加或删除产品 Total price of products and purchase产品和购买总价 Form to capture payment information表格以获取付款信息 Connection to some third party credit card processor连接到某些第三方信用卡处理器 Ability to display result from credit card processor能够显示信用卡处理器的结果 Upon success, deliver purchased product成功后,交付购买的产品
The checkout page will be styled in Bootstrap and use some AngularJS to help with a little dynamic display of the product totals. The final result will look like the following:
结帐页面将在Bootstrap中设置样式,并使用AngularJS来帮助动态显示产品总数。 最终结果将如下所示:
创建结帐页面 (Creating the Checkout Page)
To create the checkout page, we’ll need to add a view and a few model classes.
要创建结帐页面,我们需要添加一个视图和一些模型类。
Open the Views folder then Home. Right click the Home folder and select to add a new item. Select MVC View Page. Name the file Checkout.cshtml.
打开“视图”文件夹,然后打开“主页”。 右键单击主文件夹,然后选择添加一个新项目。 选择“ MVC查看页面”。 将文件命名为Checkout.cshtml。
We’re going to once again style our page using Bootstrap, which we’ve already added a reference for. The following code will make up our checkout page:
我们将再次使用Bootstrap设置页面样式,我们已经为其添加了参考。 以下代码将构成我们的结帐页面:
<form action="Checkout" method="post"><p><div class="container" ng-controller="ctrl"><div class="row"><div class="col-sm-12"><div class="panel panel-info"><div class="panel-heading"><div class="panel-title"><div class="row"><div class="col-sm-12"><h5><span class="glyphicon glyphicon-shopping-cart"></span> Shopping Cart</h5></div></div></div></div><div class="panel-body"><div class="row"><div class="col-sm-6"><h4 class="product-name"><strong>Lesson 1</strong></h4><h4><small>Beginner Lessons</small></h4></div><div class="col-sm-4"><div class="col-sm-6 text-right"><h6><strong>97.00 <span class="text-muted">x</span></strong></h6></div></div><div class="col-sm-2"><input type="text" ng-model="lesson1_1000" name="lesson1_1000" placeholder="0" class="form-control input-sm"></div></div><hr><div class="row"><div class="col-sm-6"><h4 class="product-name"><strong>Lesson 2</strong></h4><h4><small>Intermediate Lessons</small></h4></div><div class="col-sm-4"><div class="col-sm-6 text-right"><h6><strong>97.00 <span class="text-muted">x</span></strong></h6></div></div><div class="col-sm-2"><input type="text" ng-model="lesson2_1010" name="lesson2_1010" placeholder="0" class="form-control input-sm"></div></div><hr /><div class="row"><div class="col-sm-12"><div class="form-group col-sm-6"><label for="cardName">NAME</label><div class="input-group"><input type="text" class="form-control" name="cardName" placeholder="Name On Card" /></div></div></div></div><div class="row"><div class="col-sm-12"><div class="form-group col-sm-6"><label for="cardNumber">CARD NUMBER</label><div class="input-group"><input type="text" class="form-control" name="cardNumber" placeholder="Valid Card Number" /></div></div></div></div><div class="row"><div class="form-group col-sm-8"><div class="col-sm-4"><label>EXPIRATION</label></div><div class="col-sm-3"><label for="cardCv">CV CODE</label></div></div></div><div class="row"><div class="form-group col-sm-6"><div class="col-sm-3"><input type="text" class="form-control" name="cardYear" placeholder="YY" /></div><div class="col-sm-3"><input type="text" class="form-control" name="cardMonth" placeholder="MM" /></div><div class="col-sm-3"><input type="text" class="form-control" name="cardCv" placeholder="CV" /></div><div class="col-sm-3"></div></div></div><hr></div><div class="panel-footer"><div class="row text-center"><div class="col-sm-9"><h4 class="text-right">Total $<strong>{{lesson1_1000 * 97 + lesson2_1010 * 97}}</strong></h4></div><div class="col-sm-3"><button type="submit" class="btn btn-success btn-block">Purchase</button></div></div></div></div></div></div></div>
This code already has AngularJS added to it. Before we discuss the AngularJS code, let’s talk a little about the form layout and functionality.
这段代码已经添加了AngularJS。 在讨论AngularJS代码之前,让我们先讨论一下表单布局和功能。
To keep things simple, our entire checkout takes place on one form. This is accomplished by displaying available lessons on the same page. The user can increment the quantity of lessons. This will also total up the price near the Checkout button. If the user wants to remove an item, they simply set the quantity back to zero.
为简单起见,我们的整个结帐都以一种形式进行。 这是通过在同一页面上显示可用课程来完成的。 用户可以增加课程数量。 这也将合计“结帐”按钮附近的价格。 如果用户要删除项目,则只需将数量设置回零即可。
AngularJS功能 (AngularJS Functionality)
AngularJS is used to update the total price and display an error or success message. To add AngularJS to the project, open thebower.json
file and add the AngularJS reference, as shown on the last line below:
AngularJS用于更新总价并显示错误或成功消息。 要将AngularJS添加到项目中,请打开bower.json
文件并添加AngularJS引用,如下面的最后一行所示:
"jquery-validation": "1.11.1","jquery-validation-unobtrusive": "3.2.2","hammer.js": "2.0.4","bootstrap-touch-carousel": "0.8.0","angularjs": "*"
With AngularJS, we can total prices because the product quantity is captured dynamically, as shown below
使用AngularJS,我们可以总计价格,因为产品数量是动态捕获的,如下所示
<input type="text" ng-model="lesson1_1000" name="lesson1_1000" placeholder="0" class="form-control input-sm">
ng-model
binds the input text field value to lesson1_1000. We then calculate a total using:
ng-model
将输入的文本字段值绑定到lesson1_1000。 然后,我们使用以下公式计算总数:
<h4 class="text-right">Total $<strong>{{lesson1_1000 * 97 + lesson2_1010 * 97}}</strong></h4>
We take the quantity of a product and multiple it by the price of that product. To ensure we don’t get something like “NaN” in place of the total price, we need to initialize our AngularJS variables. That’s where the controller.js file comes in.
我们将产品的数量乘以该产品的价格。 为了确保我们不会得到像“ NaN”这样的总价格,我们需要初始化AngularJS变量。 这就是controller.js文件的入库位置。
To add the controller file, open the wwwroot folder, and add a “js” folder. Then right click it and addcontroller.js
:
要添加控制器文件,请打开wwwroot文件夹,然后添加一个“ js”文件夹。 然后右键单击它并添加controller.js
:
We define our AngularJS controller in this file as follows:
我们在此文件中定义AngularJS控制器,如下所示:
angular.module('myapp', []).controller('ctrl', ['$scope', function ($scope) {function init() {$scope.lesson1_1000 = 0;$scope.lesson2_1010 = 0;}init();}]);
All we’re doing is initializing our product variables to zero. Then we call theinit()
function to execute the code.
我们要做的只是将产品变量初始化为零。 然后,我们调用init()
函数来执行代码。
We do need to add the “myapp” and “ctrl” references on the client side. Open _Layout.cshtml and modify the body tag as follows:
我们确实需要在客户端添加“ myapp”和“ ctrl”引用。 打开_Layout.cshtml并按如下所示修改body标签:
<body ng-app="myapp">
Now open Checkout.cshtml and modify the “container” div as follows:
现在打开Checkout.cshtml并按如下所示修改“ container” div:
<div class="container" ng-controller="ctrl">
This ensure our product variables are within the correct scope. While we’re here, drag thecontroller.js
file into the head section of_Layout.cshtml
. This will create a reference to the controller file, as shown below:
这样可以确保我们的产品变量在正确的范围内。 当我们在这里时,将controller.js
文件拖到_Layout.cshtml
的头部。 这将创建对控制器文件的引用,如下所示:
<script src="~/js/controller.js"></script>
You probably noticed the following code at the bottom of ourCheckout.cshtml
:
您可能在Checkout.cshtml
的底部注意到了以下代码:
<div ng-hide="@Model.DisplaySuccess" class="alert alert-success" role="alert">Thank you for your purchase.<br />You can <a href="/downloads/@Model.DownloadCode">download your product here</a>.</div><div ng-hide="@Model.DisplayError" class="alert alert-danger" role="alert">Sorry. Your card was declined. Please contact your bank or use another card.</div>
ng-hide
will hide the related div when the expression within it is true. In this case, we’re only sending back a value of true or false. This value is accessed via Razor syntax. The model that we send back with the view contains the above two properties. Their values are set depending on the response of our transaction processing.
当其中的表达式为true时,ng-hide
将隐藏相关的div。 在这种情况下,我们只发送回true或false值。 可通过Razor语法访问此值。 我们随视图发送回的模型包含以上两个属性。 根据我们交易处理的响应来设置它们的值。
模型类 (Model Classes)
Before we begin modifying the controller, we’ll add a few needed model classes. Add the following files (classes) to the Models folder:Product.cs
,Form.cs
andCheckout.cs
.
在开始修改控制器之前,我们将添加一些需要的模型类。 将以下文件(类)添加到Models文件夹:Product.cs
,Form.cs
和Checkout.cs
。
InCheckout.cs
, add the following:
在Checkout.cs
,添加以下内容:
public class Checkout{public string DisplayError { get; set; }public string DisplaySuccess { get; set; }public string DownloadCode { get; set; }}
This code is only used for hiding or showing our success and error boxes. DownloadCode creates a code that can be appended to a URL, mimicking a download page for the purchased lessons. We send the Checkout class instance back with the view.
此代码仅用于隐藏或显示我们的成功和错误框。 DownloadCode创建一个可以附加到URL的代码,从而模仿所购买课程的下载页面。 我们将Checkout类实例与视图一起发送回去。
Add the following toProduct.cs
:
将以下内容添加到Product.cs
:
public class Product{public int Id { get; set; }public string Name { get; set; }public int Price { get; set; }}class FormProduct{public int Id { get; set; }public int qty { get; set; }}class ProductManager{public static int productTotal(List<FormProduct> formProducts){int total = 0;List<Product> products = ProductManager.GetProducts();foreach (var item in formProducts){var match = products.Where(x => x.Id == item.Id).FirstOrDefault();if (match != null){total += (match.Price * item.qty);}}return total;}public static List<Product> GetProducts(){List<Product> products = new List<Product>();Product lesson1 = new Product() { Id = 1000, Name = "Lesson 1", Price = 97 };Product lesson2 = new Product() { Id = 1010, Name = "Lesson 2", Price = 97 };products.Add(lesson1);products.Add(lesson2);return products;}
The above code contains all of our product related classes. Product is our main product class, containing instances of products. FormProduct is a conduit class. It is meant to translate raw form data into a Product class. ProductManager is a helper class. It totals up product prices coming in from the form. GetProducts() is a mocked method that similates getting products from a data source.
上面的代码包含我们所有与产品相关的类。 产品是我们的主要产品类别,其中包含产品实例。 FormProduct是管道类。 它旨在将原始表单数据转换为Product类。 ProductManager是帮助程序类。 它汇总了来自表单的产品价格。 GetProducts()是一种模拟方法,可模拟从数据源获取产品。
Finally, add the following class to Form.cs:
最后,将以下类添加到Form.cs中:
public class myForm{public string lesson1_1000 { get; set; }public string lesson2_1010 { get; set; }[JsonProperty("cardName")]public string cardName { get; set; }[JsonProperty("cardNumber")]public string cardNumber { get; set; }[JsonProperty("cardYear")]public string cardYear { get; set; }[JsonProperty("CardMonth")]public string CardMonth { get; set; }[JsonProperty("CardCv")]public string CardCv { get; set; }[JsonProperty("totalPrice")]public int totalPrice { get; set; }}
This class will serve a dual purpose. It will bind to our form fields, making it easier to access them in the controller. Since this instance will contain all of the payment information, wouldn’t it be nice to just use it when we submit to the ecommerce processor?
该课程将达到双重目的。 它将绑定到我们的表单字段,从而更轻松地在控制器中访问它们。 由于此实例将包含所有付款信息,因此当我们提交给电子商务处理器时只使用它会不会很好?
By adding totalPrice, we have everything needed for processing a payment, allowing us to serialize myForm and submit to the payment processor.
通过添加totalPrice,我们拥有处理付款所需的一切,从而使我们可以序列化myForm并提交给付款处理器。
捕获提交的表单数据 (Capturing submitted Form Data)
The main processing for our checkout page occurs in the HomeController.cs file. Create the following method:
我们结帐页面的主要处理过程在HomeController.cs文件中进行。 创建以下方法:
[HttpPost]public IActionResult Checkout(myForm form){Checkout c = new Checkout();List<FormProduct> products = new List<FormProduct>();if (!string.IsNullOrEmpty(form.lesson1_1000)){products.Add(new FormProduct() { Id = 1000, qty = Convert.ToInt16(form.lesson1_1000) });}if (!string.IsNullOrEmpty(form.lesson2_1010)){products.Add(new FormProduct() { Id = 1010, qty = Convert.ToInt16(form.lesson2_1010) });}form.totalPrice = ProductManager.productTotal(products);var url = "http://localhost:50672/api/values/";string serializedData = JsonConvert.SerializeObject(form, Formatting.None);serializedData = JsonConvert.SerializeObject(serializedData);using (var client = new WebClient()){client.Headers[HttpRequestHeader.ContentType] = "application/json";var result = client.UploadData(url, "POST", Encoding.ASCII.GetBytes(serializedData));var response = System.Text.Encoding.UTF8.GetString(result);var decode = JsonConvert.DeserializeObject(response);c.DisplaySuccess = "true";c.DisplayError = "true";if (response.Contains("200")){c.DisplaySuccess = "false";c.DownloadCode = DateTime.Now.Ticks.ToString();}else if (response.Contains("406")){c.DisplayError = "false";}}return View(c);}
We start by creating an instance of theCheckout
class. This won’t be used until the end of the method, when we get back our response from the mocked third party ecommerce processor.
我们首先创建Checkout
类的实例。 该方法要等到方法结束后才能使用,当我们从模拟的第三方电子商务处理器取回响应时。
Next, we convert our form fields into FormProduct instances. The Checkout() parameter is of type myForm. This classes allows binding to form fields, making them easier to access. myForm class is below and defined at the top of HomeController:
接下来,我们将表单字段转换为FormProduct实例。 Checkout()参数的类型为myForm。 此类允许绑定到表单字段,使其更易于访问。 myForm类在HomeController的下面,并在其顶部定义:
We call:
我们称之为:
form.totalPrice = ProductManager.productTotal(products);
… to get the total price of submitted products. We assign the total back to themyForm
instance. As mentioned earlier, we can submit this class to the ecommerce process by serializing it, which is what we do next.
…以获取提交产品的总价。 我们将总数分配回myForm
实例。 如前所述,我们可以通过序列化该类来将其提交给电子商务流程,这是我们下一步要做的。
You can see serliazation occurs twice. This might be more an issue with JsonConvert. By not serializing twice, our JSON is malformed and will result in a null submission to the ecommerce processor.
您可以看到序列化发生了两次。 这可能与JsonConvert有关。 如果不进行两次序列化,则我们的JSON格式不正确,将导致向电子商务处理器的提交为空。
Before we move on, let’s add a Nuget Package for the JSON serialize class – JsonConvert.
在继续之前,让我们为JSON序列化类– JsonConvert添加一个Nuget包。
Right click on the References node in the Solution Explorer. Click Manage Nuget Packages. Search forNewtonsoft.Json
. You can see in the screenshot below that I have already added it:
右键单击“解决方案资源管理器”中的“引用”节点。 单击管理Nuget程序包。 搜索Newtonsoft.Json
。 您可以在下面的屏幕截图中看到我已经添加了它:
Next, we queue up call to our Web API app. The Web API app must be running since we are referencing its local URL.
接下来,我们将对Web API应用程序的调用排队。 Web API应用程序必须正在运行,因为我们正在引用其本地URL。
client.Headers[HttpRequestHeader.ContentType] = "application/json";var result = client.UploadData(url, "POST", Encoding.ASCII.GetBytes(serializedData));var response = System.Text.Encoding.UTF8.GetString(result);var decode = JsonConvert.DeserializeObject(response);
Once the response comes back, we deserialize it and looks for specific responses. If this were a real ecommerce response, we’d likely not be simply looking for a string. There would probably be a little more structure to how the response is received.
回复返回后,我们将反序列化并查找特定的回复。 如果这是一个真实的电子商务响应,我们可能不会只是在寻找一个字符串。 接收响应的方式可能会有更多的结构。
We set values for showing and hiding the success or failure divs.
我们设置用于显示和隐藏成功或失败div的值。
A success will look like the following:
成功将如下所示:
You can see our download link for the product.
您可以看到我们的产品下载链接。
A failure will look like:
失败看起来像:
You can simply change the return type in the Web API app to simulate different responses.
您只需在Web API应用中更改返回类型即可模拟不同的响应。
电子商务处理 (Ecommerce Processing)
Ecommerce processing will basically be mocked by a Web API app. We will POST to a single endpoint in the Web API app. From this same endpoint, we’ll receive a success or failure on the transaction.
电子商务处理基本上将由Web API应用程序模拟。 我们将POST到Web API应用程序中的单个端点。 从同一端点,我们将在交易中获得成功或失败。
While ecommerce third party solutions vary largely, we are going to use a minimal but standard solution. We’ll send the user’s payment information along with a total to charge.
尽管电子商务第三方解决方案差异很大,但我们将使用最小但标准的解决方案。 我们将发送用户的付款信息以及总费用。
To create a Web API app, select File > New Project > Web Application > Web API:
要创建Web API应用,请选择“文件”>“新建项目”>“ Web应用程序”>“ Web API”:
The modifications we’ll need to make are going to be minimal. Open the Controllers folder and then theValueControllers.cs
file. Modify thePOST
method so it looks like the following:
我们将需要进行的修改最少。 打开Controllers文件夹,然后打开ValueControllers.cs
文件。 修改POST
方法,使其如下所示:
[HttpPost]public HttpResponseMessage Post([FromBody]string value){var product = JsonConvert.DeserializeObject<Product>(value);//ecommerce processingreturn new HttpResponseMessage(HttpStatusCode.NotAcceptable);}
Thevalue
parameter is JSON submitted from our website. This JSON is deserialized into a class with the help of JsonConvert, which we’ll add a reference for shortly. From there, we can do whatever is necessary to complete the ecommerce transaction.
value
参数是从我们的网站提交的JSON。 在JsonConvert的帮助下,此JSON被反序列化为一个类,稍后我们将添加一个引用。 从那里,我们可以做任何必要的事情来完成电子商务交易。
Since this is only a mock solution, we’re going to send back a status immediately. This is why the return type isHttpResponseMessage
.NotAcceptable
will send back a status code of 406, which we’ll look for in our website as a failure and present the user with an appropriate message.
由于这只是一个模拟解决方案,因此我们将立即发送回状态。 这就是为什么返回类型为HttpResponseMessage
。NotAcceptable
将发回状态代码406,我们将在我们的网站中将其视为失败,并向用户显示适当的消息。
To add theJsonConvert
reference, we’re going to add theNewtonsoft.Json
Nuget Package, just as we did in our web app. Right click on the References node in the Solution Explorer. Click Manage Nuget Packages. Search forNewtonsoft.Json
. You can see in the screenshot below that I have already added it:
要添加JsonConvert
参考,我们将添加Newtonsoft.Json
Nuget包,就像在Web应用程序中一样。 右键单击“解决方案资源管理器”中的“引用”节点。 单击管理Nuget程序包。 搜索Newtonsoft.Json
。 您可以在下面的屏幕截图中看到我已经添加了它:
If you hover over JsonConvert, you’ll see a yellow light bulb in the margin. Hover over it and you’ll get a hint to add the proper using statement for theJsonConvert
class.
如果将鼠标悬停在JsonConvert上方,您会在边缘看到一个黄色的灯泡。 将鼠标悬停在它上面,您将获得提示,为JsonConvert
类添加适当的using语句。
Now we need to create our product class. Remember, the Web API app is only mocking an ecommerce solution. But this will give us a chance to see that our submitted JSON is in fact being deserialized into a class. The following is what goes into theProduct
class:
现在我们需要创建我们的产品类。 记住,Web API应用程序只是在模仿电子商务解决方案。 但这将使我们有机会看到我们提交的JSON实际上反序列化为一个类。 以下是Product
类的内容:
class Product{[JsonProperty("cardName")]public string cardName { get; set; }[JsonProperty("cardNumber")]public string cardNumber { get; set; }[JsonProperty("cardYear")]public string cardYear { get; set; }[JsonProperty("CardMonth")]public string CardMonth { get; set; }[JsonProperty("CardCv")]public string CardCv { get; set; }[JsonProperty("totalPrice")]public int totalPrice { get; set; }}
Setting a breakpoint in the Post method, we can see what our binded data looks like from the web app:
在Post方法中设置一个断点,我们可以从Web应用程序中看到绑定数据的样子:
MVC gives us this binding free. There isn’t anything we need to make it happen except to ensure our class properties match those coming into our method.
MVC免费提供此绑定。 除了确保我们的类属性与方法中使用的那些属性匹配之外,我们无需执行任何操作。
摘要 (Summary)
We’ve extended our guitar lesson website, allowing our artist to now sell lessons directly from his site. By going with a simple solution, we’ve put the listing of products, cart and checkout all on the same page. With the help of some AngularJS, we were able to take advantage of dynamic functionality to provide instant feedback to the user.
我们已经扩展了吉他课程网站,使我们的艺术家现在可以直接从他的网站上出售课程。 通过一个简单的解决方案,我们将产品清单,购物车和结帐全部放在同一页面上。 借助一些AngularJS,我们能够利用动态功能为用户提供即时反馈。
In the next article, we’ll create a mobile app entirely in Visual Studio. The app’s purpose will be to notify the user of new lessons. By utilizing the Cordova framework, we can use our existing knowledge of web based languages to write a fully functional mobile application.
在下一篇文章中,我们将完全在Visual Studio中创建一个移动应用程序。 该应用程序的目的是通知用户新课程。 通过利用Cordova框架,我们可以使用我们现有的基于Web的语言知识来编写功能全面的移动应用程序。
翻译自: /visual-studio-community--adding-ecommerce-functionality/