构建一个真实的应用电子商务SportsStore3

 

上篇中我们已经展示数据到View, 但是这些数据都是来自于我们的mock IProductRepository,在我们真正的实现repository之前,我们需要创建一个SQL Server数据库并添加一些数据。
 
我们将使用EF框架操作SQL Server数据库, EF是一个.net ORM框架,ORM框架能让我们想操作对象实例一样操作数据库的表、列、行。就像使用正常的C#对象,这么做需要一点Linq的知识,Linq不是什么神秘的、高难的东西,相信所有人都能够在想当短的时间内掌握并使用LInq。
 
点击View菜单,打开服务器资源管理器,点击连接到数据库。你会看到连接对话框,设置服务器名为(localdb)\v11.0,这是一个特殊的名字,表示你要使用本地数据库的特性。VS2012新添加了一个特性,就是可以使用SQL Server的内核创建一个免管理员的本地数据库,相关的详细使用说明,请参见相关文档,这里就不细说了。现在,请确保你选择了windows认证登陆方式,设置数据库名为SportsStore。如下图:
 
点击确定,然后会出现确认对话框,点击yes,就会创建一个新的数据库。
 
我们现在只有一个Product表,右击表文件夹,选择添加新表:
 
创建新表可以有2种方式,这里我们使用T-SQL方式,因为它更精准,更方便,建表语句如下:
CREATE TABLE Products
(
[ProductID] INT NOT NULL PRIMARY KEY IDENTITY,
[Name] NVARCHAR(100) NOT NULL,
[Description] NVARCHAR(500) NOT NULL,
[Category] NVARCHAR(50) NOT NULL,
[Price] DECIMAL(16, 2) NOT NULL
)
这个表与我们之前定义的Product model 类略有不同,现在点击更新按钮,你会看到一个更新的Summary
如下:
点击更新数据库按钮。表就会被创建出来,我们需要添加些数据到这个表中。
 
创建Entity Framework Context
最新版的EF包含了一个很不错的特性,叫做code-first,意思是我们能在我们的model中定义一些类,然后
从这些类生成数据库。在此,我不想去谈论这种技术的优缺点。相反,我将基于这个code-first,展示给你一个变体,我们将整合我们的model类和一个存在的数据库,现在我们先安装EF框架到我们的项目。
打开NuGet工具包管理程序,安装EF框架,如下图:
 
 
 
现在,在SportsStore.Domain工程中创建一个文件夹,叫做Concrete,然后添加一个文件,命名为EFDbContext,代码如下:
 
using SportsStore.Domain.Entities;
using System.Data.Entity;
 
namespace SportsStore.Domain.Concrete
{
    public class EFDbContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
    }
}
 
为了使用code-first特性的优势,我们需要创建一个类,它派生自 System.Data.Entity.DbContext. 这个类会为数据库中的每个表自动定义一个属性,并指定表名为属性名,我们现在的属性名是Products,并且类型参数是Product. 我们希望Product model被用作展示  Products表中的行。现在我们要告诉Entity Framework怎样去连接数据库,所以我们要添加一个连接字符串到SportsStore.WebUI工程的Web.config文件中。
 
<connectionStrings>
<add name="EFDbContext" connectionString="Data Source=(localdb)\v11.0;Initial
Catalog=SportsStore;Integrated Security=True"
providerName="System.Data.SqlClient"/>
</connectionStrings>
 
创建Product Repository
 
现在,我们要为实现真是的数据操作添加一个文件到 Concrete文件夹中,叫做EFProductRepository。编辑这个文件,看起来像下面的样子:
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Linq;
namespace SportsStore.Domain.Concrete {
public class EFProductRepository : IProductsRepository {
private EFDbContext context = new EFDbContext();
public IQueryable<Product> Products {
get { return context.Products; }
}
}
}
 
这个repository 类实现了 IProductRepository接口并使用一个EFDbContext 实例去数据库中取数据。你将看到EF是怎么样 工作的。现在我们需要编辑NinjectControllerFactory类,用真正的数据替换Mock 对象。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using Moq;
using Ninject;
using SportsStore.Domain.Concrete;
 
namespace SportsStore.WebUI.Infrastructure
{
    public class NinjectControllerFactory: DefaultControllerFactory
    {
 
            private IKernel ninjectKernel;
 
            public NinjectControllerFactory() {
                ninjectKernel = new StandardKernel();
                AddBindings();
            }
 
            protected override IController GetControllerInstance(RequestContext
                requestContext, Type controllerType) {
 
                return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType);
            }
 
            private void AddBindings() {
 
                Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
 
                //mock.Setup(m => m.Products).Returns(new List<Product> {
                //    new Product { Name = "Football", Price = 25 },
                //    new Product { Name = "Surf board", Price = 179 },
                //    new Product { Name = "Running shoes", Price = 95 }
                //}.AsQueryable());
                //ninjectKernel.Bind<IProductsRepository>().ToConstant(mock.Object);
                ninjectKernel.Bind<IProductsRepository>().To<EFProductRepository>();
            }
         }
    }
 
运行你的项目,你将看到如下画面:
 
下一篇中,我们将添加更多商品,并应用分页技术,让用户能够浏览我们商品,并可以从一个页面移动到另一个页面,我们也将逐步的加入购物车等功能,为您一步一步地展开这个项目,如果您喜欢它,请继续关注我的续篇!

 

 

 

 

 

 
标签: MVC4

事件触发的一个细节设计

前端开发过程中,事件机制无处不在。比如使用 jQuery 添加 DOM 事件:

复制代码
$(document).click(function() {
  console.log(1);
});

$(document).click(function() {
  console.log(2);
});
复制代码

当点击 document 时,控制台中会按照预期输出 1 和 2 。

问题来了:

复制代码
$(document).click(function() {
  console.log(1);
  DOES_NOT_EXIST++;
});

$(document).click(function() {
  console.log(2);
});
复制代码

以上代码,点击 document 时,控制台中会输出:

1
Uncaught ReferenceError: DOES_NOT_EXIST is not defined

输出了 1,然后抛了一个异常,没有输出 2 。

如果使用浏览器自身的 addEventListener 注册:

复制代码
document.addEventListener('click', function() {
  console.log(1);
  DOES_NOT_EXIST++;
}, false);

document.addEventListener('click', function() {
  console.log(2);
}, false);
复制代码

当点击 document 时,在 Chrome 下,控制台中会输出:

1
Uncaught ReferenceError: DOES_NOT_EXIST is not defined
2

很明显,当 handler 中有异常时,浏览器的 addEventListener 与 jQuery 的处理方式不一样:

  • 继续执行策略:浏览器会抛出异常,然后继续执行其他 handlers 。
  • 停止执行策略:jQuery 会抛出异常,然后停止执行其他 handlers 。

继续讨论前,先留一个小作业:大家可以研究下 YUI、MooTools、Prototype、Dojo 等等类库框架的处理策略。回复给我,全答对者,明天有惊喜。

对于继续执行策略,核心理念是: 事件 handlers 之间应该彼此无依赖,即便有异常也不能影响其他 handlers 的执行。实现上可以通过 try catch 或 setTimeout 等方式,来确保一粒老鼠屎不会坏掉一锅汤。这个理念有很多人、很多类库框架支持。

对于停止执行策略,核心理念是: 事件 handlers 之间应该彼此无依赖,但当某个 handler 异常时,不应该假装没事一样,继续执行其他 handlers 。这个理念也有很多人、很多类库框架支持。因为掉进锅里的老鼠屎很可能有毒,一旦发现了,最明智的做法是别让大家喝了。

无论是继续执行还是停止执行,都同意事件 handlers 之间应该彼此无依赖,这一点上无分歧。但涉及异常时,两种理念下的策略迥异。

这两种处理策略,究竟哪种更好呢?你的想法是怎样的?

两种策略的分歧

同一个事件的 handlers 在触发过程中,当执行某个 handler 发生异常时,昨天提到有两个处理策略:继续执行和停止执行。

目前支持继续执行的类库框架有:MooTools、Prototype、Dojo
目前支持停止执行的类库框架有:YUI3、jQuery、Backbone

这个列表不能说明什么,但值得注意的是,这个问题在 2009 年时,JavaScript 大神 Dean Edwards 就在 Callbacks vs Events 一文中提出过,并且给出了一个非常 Geek 的解决方案。

我印象中,Prototype 等类库,就是在 Dean Edwards 指出这个问题后,将策略修改成了继续执行。

然而,目前更流行的几个类库 jQuery、YUI3 包括新秀 Backbone 等,却依旧坚持停止执行。并非是他们不知道,而是这几个类库的作者,选择了停止执行策略。

这两种策略的主要分歧在于:

  1. 继续执行策略觉得,继续执行是对 handlers 之间无依赖的更好保障。如果停止执行,就破坏了无依赖性,使得后面 handlers 的执行依赖前面 handlers 的无异常性。

  2. 停止执行策略觉得,发生异常时,已经超出了无依赖性的讨论范畴。在类库里面 try catch 或通过其他方式处理都不是最佳解决方式,这应该交给用户去解决,属于 user-land 范畴。

Backbone 作者 Brad Dunbar 的 观点 如下:

While I understand your concern, suppressing errors inside event handlers is a much worse behavior than skipping the rest of the handlers. When something fails, you want to know immediately, not continue as though nothing happened.

大意是说:

与停止执行相比,在事件处理器中抑制错误是一种更糟糕的行为。当某些事情不对时,就应该立刻知道,而不是装着什么也没发生一样继续执行。

jQuery 开发者也有类似的 观点

In order to continue subsequent callbacks, jQuery would have to catch the error, which is not a good solution. If an error is acceptable, a try/catch can be implemented by the user.

大意是:

为了继续执行回调,jQuery 需要捕获错误,这并不是一个好的解决方案。如果某个错误是可以容忍的,那么应该由用户通过 try / catch 去实现。

放在场景中思考

但为什么浏览器的默认行为是继续执行呢?

我的想法是,得分场景来说:展现型页面和功能型页面。

对于展现型页面,比如淘宝首页,页面某一个区域出问题时,最好不要影响其他区域的展现。因为一般来说,各个区域之间不会有依赖。感觉这也是浏览器设计之初,采取继续执行策略的初衷。这个初衷还体现在,当某个 script 块的代码发生异常时,不会影响其他独立 script 块的执行。

对于功能型页面来说,比如 Gmail,当页面某一个区域出问题时,经常意味着底层数据或网络出了问题,这时最好的处理方式是,都停下来,统一给出错误或重试提示,而不是继续进行操作。因为操作已经不可预期,很可能造成不必要甚至错误的操作,比如发出一封错误的邮件等等。

无依赖很难

Backbone 的使用场景应该是功能型页面,因此非常坚持采用停止执行策略。类似 YUI3 也是如此。jQuery 更多是觉得这应该是用户范畴的事,类库不应该处理。

举个例子,对于支付宝来说,由于支付操作涉及用户金额,有可能存在以下可能性:

  1. handler A 检查校验码,有可能通过,有可能不通过。通过时,会设置某个校验标识为 true 。
  2. handler B 提交支付请求,提交前会检查是否通过校验。
  3. 当 handler A 出错时,校验标识有可能是旧值,也有可能被设置成错误值。
  4. handler B 并不依赖 handler A,但依赖校验标识。当 handler A 出错时,校验标识无论是什么值,都已经不可靠,即便是校验通过,也不应该提交支付请求。

这就是说,对于功能型页面来说,一旦有代码错误(不一定是 handler 引发的),就应该尽可能做到停止代码执行,并告知用户出了问题。

这就如一锅汤,一旦滴进了一滴毒药,只要发现有一个人中毒了,最明智的做法就是立刻不再继续把汤盛给其他人,否则毒死一批人,罪孽就大了。

问题的核心是,要判断滴进汤里的是毒药,还是仅仅是一粒沙子。对展现型页面来说,经常是沙子,无伤大雅,但对功能型页面来说,我情愿假设都是毒药,应立刻告知所有人并停止喝汤。

这个例子的背后,还能让我们看到无依赖的 handlers 之间并不一定无依赖。由于代码运行在同一个环境下,有可能共享同一份数据。对于前端代码来说,明显共享的是同一份 DOM 树。这样,当某个 handler 出了问题后,很可能共享的数据、DOM 树已经不可靠。继续执行其他 handlers,很可能已经是在一个不可靠的环境中去运行代码。后续代码已经不可控,特别是对于复杂系统来说。

对于复杂系统,try / catch 并不能保障无依赖性。因为环境的复杂性,继续执行反而可能带来后续的不可控性。

范畴很重要

Backbone 和 jQuery 社区中,这个问题其实被反复提出过,Arale 中也被 提出过。但我始终觉得,在基础类库中去try / catch 并不是最佳解决方案。不光不是最佳方案,更重要的是,这件事,不应该属于类库去解决的,而应该是用户需要去考虑的。

比如,如果用户担心某个 handler 有可能会出问题,那么这个 handler 在可能出问题的地方,本就应该自行try / catch,由用户去负责。对于复杂系统,对于不放心的 handlers,可以通过工厂模式自动封装。比如很多游戏的代码里,会做类似的错误异常统一处理。但具体应该对哪些 handlers 封装异常,由具体游戏的开发者决定。

还有一个有意思的是,少就是多。类库做得越少(保持完整性),用户能做的反而越多。假设类库封装了 handler 的异常,那么对于那些想采取停止执行策略的场景来说,就很不好实现了。反之,则用户自行封装就好。

Sea.js 从 1.x 升级到 2.0,最核心的一个思考就是缩减范畴,不断思考 Sea.js 应该做什么,不应该做什么,砍掉了大量功能,增加了少量功能,目前看起来还是挺不错的。但即便经过半年的升级后,Sea.js 2.0 里,目前依旧发现有少量功能不应该提供,打算在接下来的版本里进一步去掉。

少即是多,确定边界对类库框架来说非常非常重要。

小结

对于展现型页面来说,采用浏览器的继续执行策略,个人觉得是合理的。

对于功能型页面来说,特别是涉及复杂系统时,基础类库中应该尽量少做一些事情,把更多的决定权交给用户。

也许无法说服你,其实也不需要达成某个最终结论。不同应用中,这两种策略都有合适的使用场景。

 
 
 
标签: javascript
原文地址:https://www.cnblogs.com/Leo_wl/p/3114898.html