zoukankan      html  css  js  c++  java
  • 构建一个真实的应用电子商务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
  • 相关阅读:
    27_Go基础(defer)
    30_Go基础(接口)
    29_Go基础(结构体)
    关于PLSQL Developer报"动态执行表不可访问,本会话的自动统计被禁止"错的解决方法
    用JSP实现上传文件的两种方法
    rownum in ORACLE
    sql笔试语句大全
    oracle下载地址以及安装教程
    对于js中eval()函数的理解 和 写一个函数trim() 去掉字符串左右空格
    Java IO流 FileOutputStream类 和 FileInputStream类 FileReader类和FileWriter类
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3114898.html
Copyright © 2011-2022 走看看