[架构]实战剖析三层架构3:不要说BLL没有用

mikel阅读(997)

    在本系列的第一篇中,笨熊已经介绍过BLL层的功用,也说明了为什么许多人觉得这一层可有可无。但看近期博客园的文章,还有不少朋友对BLL的存在仍有疑问。正好今天碰到的这个问题颇具代表性,写出来让这些朋友对BLL在业务系统中的功用有个具体的感觉。
    先简要介绍一下要做的功能,在程序中,用户输入商品的商品编码,系统从数据库中提取显示此商品的详细信息(图1)。如果一个商品有多个供应商,则弹出提示框供用户选择某一供应商(图2)。

    图1

    图2

功能上来说很简单,先定义一个实体类:

Code

  然后数据层负责提取数据:  

Code

    逻辑层没什么用,直接调用DAL的方法得到数据并返回。

    表现层(这里是WinForm窗体)调用逻辑层方法得到数据,并判断数据条目,如果是一条,则直接显示;如果是多条,则弹出提示框让用户选择,选好后再显示(代码略)。

 

    编写好后运行,代码没有问题,但是在批量测试时发现了几种特殊情况。

    1、一些商品没有提供条码(如蔬菜、水果等散装商品),因此需要根据商品编号生成条码。

    2、一些商品除了本身的条码外,在商品的包装箱上还有条码(比如啤酒、露露、牛奶等,瓶上的条码和箱上的条码是不一样的。因此买一箱时只需扫描包装箱上的条码即可,而不用拆箱扫描里面的商品)。

    3、一些商品只有外包装箱上有条码,商品本身却没有条码,但在销售时还需要拆开来按单个商品销售,这时也要根据商品编码生成条形码。

    考虑这三种情况,那么从数据库中查询时,返回的结果有以下几种(按条码、条码类型、编码、名称、供应商显示,其它列略。条码类型列,A表示条码为商品本身条码,B表示条码为包装箱条码)。

    (1)一般情况(结果返回1 – n条数据)

    692590758024,A,186202,舒尔但莫代尔女单衣,唐山XX有限公司

    (2)商品本身没有条码(结果返回1 – n条数据)

    NULL,NULL,80604,散大米,唐山XX有限公司

    NULL,NULL,80604,散大米,天津市XX加工厂

    (3)商品本身有条码,包装箱还有条码(结果返回n条数据)

    690180818886,A,104992,露露,河北XX股份有限公司

    690180818882,B,104991,露露,河北XX股份有限公司

    690180818886,A,104991,露露,锦江XX有限公司

    690180818882,B,104991,露露,锦江XX有限公司

    (4)商品本身无条码,包装箱有条码(结果返回1 – n条数据)

    692590751824,B,101974,双汇王中王,唐山XX有限公司

    692590751824,B,101974,双汇王中王,河北XX有限公司

    这样汇总整理一下,对于重数据库中查询返回的结果,要做如下处理:首先判断条码是否为空,如果空则生成条码。确认条码不为空后,判断条码类型中是否全是B。如果有A有B,则进行筛选,保留类型为A的条目。如果全是B,则将结果中的条码全部用自动生成条码替换。

 

    确定了处理过程以后,决定需要将此过程放在何处。首先考虑的是数据层,如果能直接提取出所需结果那是最好的,但是有几点原因决定了不能放在数据层:

    (1)数据层的这个方法提供的数据具有通用性,逻辑层中不光一个方法对其进行调用,更改了返回结果会造成逻辑层的其它方法无法使用此结果。

    (2) 这个方法的查询语句非常简单,如果增加了此过程,则SQL语句的编写难度会加大,而且很难用1条语句处理这几种情况,必须实现的话,则此方法的SQL查询 语句需要改为3句,还要提前执行一条SQL判断语句来决定使用那个SQL查询语句。成倍增加了编写难度与执行时间。

    所以数据层不是进行处理的好地方。 

    再 看看表示层,如果放在了表示层,则需要在主窗口(图1)进行判断,对数据进行处理,这样将逻辑处理代码与显示代码混合在一起,不但乱,而且加大了编码量, 因为每个逻辑分值里面都要编写相应的显示代码。所以放在表示层也不是个好主意(呵呵,一开始感觉简单,真的想不建逻辑层直接合并在表示层中着)。

    因此,最适合放置此处理过程的地方,就是业务逻辑层了。而且此处的工作就是业务逻辑的处理,即使没有上述问题,也应该放与此层。

Code

 

[C#].net的提供者模型

mikel阅读(865)

使用提供者模型的好处:使用提供者模型的系统将具有很好的弹性和可扩充性。

例如你可以使用SQL server数据或者是Oracle数据库做为你的数据源,并且只需要改变一下web.config文件。

 

下面以一个公司的人员信息的存储进行讲解。现在我们用来存储人员信息(personInfo)的数据库是

SQL server2005,考虑到以后会使用其他数据库(例如mySQL,oracle),我们打算用提供者模型方式进行 系统设计。

1:web.config的配置,增加如下自定义配置节

<Company personInfoProviderName="sqlProvider">
 
<PersonInfoProviders>
    
<add name="sqlProvider" type="Company.SqlPersonInfoProvider"/>
 
</PersonInfoProviders>
</Company>

 2: 如果以后改用其他方式进行数据存储,可以这样修改

<Company personInfoProviderName="mysqlProvider">
 
<PersonInfoProviders>
  
<add name="sqlProvider" type="Company.SqlPersonInfoProvider"/>
  
<add name="mysqlProvider" type="Company.MysqlPersonInfoProvider"/>
 
</PersonInfoProviders >
</Company>

3:还没有完,你还得在web.config里增加如下代码

<configSections>
  
<section name="Company" type="Company.CompanyProvidersSection,Company"/>
</configSections>

 以上代码必须紧跟在<configuration>的下面,否则会出错。

 4:好了,这才开始我们的C#代码,他们所在的命名空间是Company.

首先新建三个类:PersonInfoProvider,SqlPersonInfoProvider,MysqlPersonInfoProvider。

 PersonInfoProvider是个abstract类,它继承ProviderBase,它的作用是提供抽象的方法让

SqlPersonInfoProvider和MysqlPersonInfoProvider来实现。

例如你要增加或删除人员,那么你首先要在PersonInfoProvider里增加两个抽象的方法:

public abstract void DeletePerson();

public abstract void AddPerson();

这只是第一步,第二步你还得在SqlPersonInfoProvider里进行具体的实现。

 5:你还要在新建一个类:CompanyProvidersSection,这个类的属性主要是和web.config的属性相互对应,

然后通过这个类来或得我们的提供者到底是谁。

 


 1public class CompanyProviderSection
 2{
 3  [ConfigurationProperty("personInfoProviderName")]
 4  public string PersonInfoProviderName()
 5 {
 6   get{return (string)this["personInfoProviderName"];}
 7 }

 8 
 9 [ConfigurationProperty("PersonInfoProviders")]
10 public ProviderSettingsCollection PersonInfoProviders
11 {
12   get{return (ProviderSettingsCollection)this["PersonInfoProviders"];}
13 }

14
15}

 

6:好,写到这里,但最重要的还没写,就是如何或得并且使用提供者。我们现在需要添加人员信息。

 


class UseProvider
    {
        
private static PersonInfoProvider _provider;
        
private static CompanyProvidersSection _providerSection;
        
private static bool _isInitialized = false;
        
private static PersonInfoProvider Provider
        {
            
return _provider;
        }
        
        
public static void AddPerson()
       {
            Provider.AddPerson();
       }  
        
private static void Initialize()
        {
            
if(!_isInitialized)
            {
                _providerSection 
= (CompanyProvidersSection)ConfigurationManager.Getsection("Compnay");
                _provider 
= (PersonInfoProvider)ProviderHelper.InstantiateProvider(_providerSection.PersonInfoProviders[_providerSection.PersonInfoProviderName]);
                _isInitiallized 
= true;
            }
        }
    }

 

 

[MSQL]利用typeperf工具收集SQL Server性能数据

mikel阅读(656)

一.利用TypePerf.exe命令行工具把Windows操作系统的性能计数器数据记录到数据库中

 

可以在作业中建立以下脚本

1.启用xp_cmdshell

默认情况下,SQL server2005安装完后,xp_cmdshell是禁用的(可能是安全考虑),如果要使用它,可按以下步骤

 

允许配置高级选项

EXEC sp_configure 'show advanced options', 1

GO

重新配置

RECONFIGURE

GO

启用xp_cmdshell

EXEC sp_configure 'xp_cmdshell', 1

GO

重新配置

RECONFIGURE

GO

 

2.定时开启,开始记录性能计数器日志

实现的功能:将“MyCounters.txt”文件中列出的计数器,按照每秒钟的时间间隔,记录到SQL数据库中,"SQL:SystemLog!TestSystemLog"ODBC数据源建立的系统DSN

EXEC xp_cmdshell 'typeperf -cf c:\MyCounters.txt -si 5 -f SQL -o SQL:SystemLog!TestSystemLog'

 

–"MyCounters.txt"可以利用下面的命令在CMD命令窗口中执行后生成

TYPEPERF qx "Processor" >>c:\MyCounters.txt

 

3. 定时关闭

结束typeperf.exe的进程

EXEC xp_cmdshell 'tskill typeperf'

 

4.关闭xp_cmdshell

用完后,要记得将xp_cmdshell禁用(出于安全考虑)

允许配置高级选项

EXEC sp_configure 'show advanced options', 1

GO

重新配置

RECONFIGURE

GO

禁用xp_cmdshell

EXEC sp_configure 'xp_cmdshell', 0

GO

重新配置

RECONFIGURE

GO

 

5.利用数据库中记录的日志分析性能数据

 

二.带来的好处:

1.  可以根据计划定时抓取服务器性能数据包括CPU、内存和磁盘利用率以及SQL Server特定数据。为数据库服务器的性能分析提供帮助。

 

2.  可以根据数据库中记录的日志结合Reporting Service绘制性能分析的报表。

 

3.  可以定制作业实现自动化

 

 

三.带来的影响:

1.  会增加服务器的负载,在测试时观察了一下这个命令的开销,基本上启动后8M左右的内存运行,开销比较小

 

另外:如果考虑实施的话,可以新建一个数据库(专门用于监控服务器性能等的表),便于维护和管理

[MVC]ASP.NET MVC中文文档

mikel阅读(866)

喜欢MVC的轻快优雅,所以把官方的翻译一下,分几次发上来:

ASP.NET Model-View-Controller Applications

介绍

Model-View-Controller (MVC) 模式分类了一个 MVC Web application的各个组件. 这种分离让你对每个独立的部分有了更多的控制,这能让你更容易的开发、修改、测试他们。
下面的主题描述了 ASP.NET MVC framework 和如何创建一个MVC applications. 以及如何创建 MVC 单元测试项目,可以让你使用测试驱动的开发技术 (TDD) 来开发 MVC applications.另外, 还告诉你如何通过使用controller-actionframework-helper方法来渲染views. 也解释了如何用action filters来限制对 action 方法的访问, 处理错误,缓存action方法的输出, 以及如何通过优先级来控制filters程序是在action 方法运行之前还是之后来执行.

ASP.NET MVC 概览

Introduction

Model-View-Controller (MVC) 架构模式将一个应用程序分离成主要的三个组件: the model, the view, and the controller. ASP.NET MVC 框架提供了一种ASP.NET Web Forms模式的替换,让你可以创建给予MVC Web应用. ASP.NET MVC 框架是一个轻量级的, 高可测试性的显示框架,并集成了现有的ASP.NET 特性, 比如 master pages membership-based 权限. MVC framework 定义在System.Web.Mvc 命名空间,以System.Web 空间为基础和支撑.
MVC
是一个许多开发人员都熟悉的开发模式.一些类型的Web应用将会受益于MVC framework. 一些将继续使用传统的基于Web Forms postbacks ASP.NET application 模式. 另外一些将同时使用两种类型,他们互相并不排斥.
MVC framework
包含下边的这些组件:

 

Models. Model 对象程序中实现逻辑和数据域的地方. 通常, model 对象检索和存储 model 状态到数据库. 例如,一个产品对象需要从数据库中检索信息,对它进行操作, 并把信息更新会数据库中的Products.

在小型项目中, model通常是一个概念上的分离,而不是在物理上存在. 例如, 如果一个程序仅仅是读取dataset 和发送数据到 view, 这个程序没有一个物理上的 model 层以及相关的类. 在这种情况下, dataset 就取代了model 对象的角色。

Views. Views 是用来显示程序的用户界面的部分(UI). 典型的, UI是从model 数据中创建的. 比方说一个Products表的编辑视图显示text boxes, drop-down lists, and check boxes,是基于Products对象的当前状态的.

Controllers. Controllers 是用来处理用户交互,与model 协同工作,决定选择哪个View来渲染UI的组件. MVC程序中, view只负责显示信息; controller 负责处理、响应用户输入、交互. 比如, controller 处理query-string 的值, 并传递这些值到model, 以用来查询数据库。

MVC 模式可以帮助你创建关注分离的应用(输入逻辑, 商务逻辑, UI逻辑), 这些元素之间是松耦合的. 这个模式指定了每种逻辑应该放在应用程序的什么位置. UI逻辑属于view. Input逻辑属于controller. 商业逻辑属于model. 这种分离能帮助你管理程序中的复杂性, 因为它允许你在某个时间点只关注实现的一个方面. 比如, 你可以焦点在view而不用依赖逻辑层.
在管理复杂性上, MVC模式比Web Forms更易测试. 比如, Web Forms,单个类被同时用来显示输出和响应用户输入. 编写Web Forms自动测试是复杂的,因为测试一个独立的页面, 你必须实例化page,所有的子控件,和依赖的其他类. 因为这么多类都要实例化来运行这个页面, 很难为程序中独立的部分写出排他的测试用例来. Web Forms写测试用例要比MVC难得多. 此外, 测试Web Forms需要一个Web server. MVC 框架解耦了各个组件而且强烈依赖接口, 这样就可以测试独立的一个组件而把框架的其他部分隔离开来.
MVC
中三个组件的松耦合也促进了平行开发. 例如,一个开发人员开发view, 第二个开发人员可以开发controller的逻辑, 第三个开发人员可以关注model中的商业逻辑。

决定什么时候来创建一个 MVC Application

你需要仔细考虑实现一个Web application到底是用ASP.NET MVC还是Web Forms. MVC并不能替代Web Forms; 你可以同时使用. (如果你已有一个基于Web Forms的程序,他们能像往常一样运行良好.)
在你决定使用MVC或者Web Forms,请衡量各自的优点。

基于MVCWeb程序的优点

ASP.NET MVC提供了以下优点:

通过分离一个程序为model, the view, controller,将更容易管理其复杂性.

它不使用view state 和服务端表单. 这对于想完全控制程序行为的开发人员来说MVC更加理想.

它使用前置控制模式通过单个的控制器来处理Web请求. 这让你可以设计一个程序来支持丰富的路由功能. 更多信息, 请看 Front Controller MSDN 网站.

对测试驱动的开发提供更好的支持 (TDD).

它支持大团队开发,并给那些需要高度控制程序行为的开发者提供了更多的支持.

基于Web Forms Web程序的优点

Web Forms提供以下优点:

它支持事件模型在HTTP协议下保持状态信息, line-of-business Web程序开发从中受益. Web Forms提供了一系列的事件来支持成千上百的服务端控件.

他使用Page Controller 模式来添加功能到单独的页面. 更多信息, Page Controller MSDN网站.

它使用view state和服务端表单, 让状态管理更容易.

在小型开发团队中他工作良好,而且设计人员可以以利用大量的组件来进行快速的开发.

通常, 用它开发程序更简单, 因为组件高度集成而且比起MVC通常代码量更少.

ASP.NET MVC的特性

ASP.NET MVC提供以下特性:

分离的程序任务(输入逻辑, 商务逻辑, UI逻辑), 可测试得, 测试驱动的开发 (TDD). MVC中所有的核心契约都是基于接口的而且能使用模拟对象进行测试, 这些模拟对象可以模仿实际对象的行为. 你可以对controllers进行单元测试并不需要启动ASP.NET进程, 这让单元测试更快更富弹性. 你可以使用任何兼容.NET的测试框架.

是一个可扩展的插件化的框架. ASP.NET MVC的组件被设计成很容易替换和自定义的. 你可以插入你自己的视图引擎, URL路由策略, action-method参数序列化, 和其他组件. ASP.NET MVC也支持依赖注入 (DI)反转控制(IOC)容器模式. DI允许你注入对象到类, 而不必自己创建它的实例. IOC指出如果一个对象需要另一个对象, 第一个对象可获得第二个对象通过外部来源,比如一个配置文件.这让测试更容易.

更强大的URL映射组件让你的程序拥有可理解的可查询的URLs. URLs不包含文件扩展名, 且设计成URL 命名模式在搜索引擎中得到优化 (SEO) 表象化状态转变(REST)的地址.

支持ASP.NET中已存在的页标记 (.aspx文件), 用户控件 (.ascx 文件), 模板页 (.master 文件). 你可以使用已有的ASP.NET的特征, 比如嵌套模板页, in-line 表达式 (<%= %>), 声明的服务端控件, 模板, 数据绑定, 定位, 等等.

支持已有的ASP.NET特征. ASP.NET MVC forms验证 Windows 授权, URL 授权, membership,  角色, 输出缓存, session profile 状态管理, 健康监视, 配置系统, provider架构.

[MVC]Kigg框架

mikel阅读(963)

Kigg – Building a Digg Clone with ASP.NET MVC Part – 1

Published: 20 Feb 2008
By: Kazi Manzur Rashid
Download Sample Code

Learn how to develop a Digg-like application with ASP.NET MVC, LINQ to SQL and ASP.NET AJAX.

Introduction

For the last few days, I have been trying to get my hands dirty with the new ASP.NET MVC framework. I saw many discussions on some of the advanced topics like IoC Container/DI, View Engine, Controller factory and so on, but I could not find a simple application to harness the power of the new ASP.NET MVC framework. Certainly, knowing these things is an added benefit but it is not mandatory to develop applications with the ASP.NET MVC Framework. In this article – from the DotNetSlackers team – I will present a basic version of Digg / DotNetKicks kind of application developed with the ASP.NET MVC framework. You will find the whole application running at the following link:

[Live Demo]

Note: The article and code are based on the first preview release of the ASP.NET 3.5 Extensions. We will be updating it as per the new release.

Prerequisites

A brief Introduction to the ASP.NET MVC Framework by Scott Guthrie:

An excellent Screencast by Scott Hanselman.

Overview

The MVC (Model-View-Controller) is a popular pattern to develop UI-centric applications based on a simple concept: divide the implementation into three logical components:

  • Model,
  • View,
  • Controller.

The ASP.NET MVC Framework is an implementation of the MVC pattern, and has built-in support for developing web applications. Let's take a quick look at these three components.

Figure 1: The MVC Framework

images/mvc.jpg

  • Model: Refers to the domain logic of your application. Usually the state of the Model is persisted in the database. Consider it as the middle tier in a typical n-Tier application, which consist of the business logic and domain objects.
  • View: Is typically the User Interface, which is used to show the Model data and take input from the User.
  • Controller: Is responsible for handling the user interaction. This is the Ultimate Driver of the whole ASP.NET MVC Framework. Depending upon the User gesture it decides which action to perform on the model; it also builds the view data and finally decides which View to render.

The ASP.NET MVC Framework is an alternate and better way to develop web applications, comparing to the regular web form model. It provides us with:

  • Clean Separation of Concerns, where each component is serving only one purpose. Thus it also gives us the opportunity to integrate TDD (Test-Driven Development) in the development workflow and unit test each component without considering the others, as most of the framework components are interface-based. This allows us to mock them out.
  • The whole framework is very much extensible and pluggable. It is really easy to replace or customize each part without affecting the others.
  • Pretty/SEO (Search Engine Optimization) URLs. Full controls of how URLs are constructed. No need to do URL Rewriting anymore.
  • True stateless model of the web. We no longer have to deal with postbacks and ViewState.
  • Full control of the generated HTML. This means no more naming containers.
  • Utilize the existing knowledge of ASP.NET like Providers, Caching, Configuration and so on.

Request Flow

In regular ASP.NET web form applications, URLs are usually mapped to physical disk files. When a URL is requested, the code of the associated file gets executed, which generates the HTML. However, in the ASP.NET MVC Framework the URLs are tied with the Controllers rather than the disk file. The component that is responsible to map the URL with the Controller is the Routing Handler. When the application starts, it is required to register the URL Routing Rules, which the Routing Handler uses to map the controller when the request arrives. Let's take a quick view of how a request is carried in different layers of the ASP.NET MVC Framework:

Figure 2: The Request Flow

images/request.jpg

  • The user requests a URL.
  • The ASP.NET MVC Framework evaluates the requested URL against the registered Routing Rules and finds a matching Controller. The Framework forwards the request to the matching Controller.
  • The Controller calls the Model to build ViewData. It can call the model multiple times depending upon the ViewData that it is building.
  • The Model, as mentioned earlier, is the middle tier, which might involve data access components, workflow activities, external web service dependencies etc. The Model returns the requested data to Controller.
  • The Controller selects a View and passes the data to the View which it previously got from the Model. The View Renders the data and returns the generated HTML to the User.

Default Conventions

There are few default conventions that we have to know before moving forward.

First, when matching the Controller for the incoming request, the framework uses a pattern UrlPathController to match the Controller. For example, if the request is for http://www.example.com/Home, it will match the HomeController for that request. Once the request reaches the Controller, the Controller executes a specified action from the URL sub-path, or it executes the default action if the action is missing in the URL. The default action for a Controller is specified when the URL Routing Rules are declared in the application start event handler. The actions are declared in the Controller class as methods. For example, if the request is for http://www.example.com/Home/Index, it will automatically execute the Index method of the HomeController; and if the URL contains more sub-paths, it will pass each sub-path as a parameter value of that method.

Next, when creating an ASP.NET MVC Project in Visual Studio, it automatically creates Controllers, Models and Views folders. It is recommended to create the Controllers, Models and Views in the corresponding folders. However, if you are developing a large application you can choose to implement the Model in one or more different projects, but the Controllers and Views must be present in the MVC Project. For each Controller, there should be a folder under the Views with the same name. For example, if we have a Controller named HomeController, there should be folder named Home under the Views folder. If a single view is used by more than one Controller, it should be placed under the Shared folder of the Views. The shared folder can also contain shared User Controls, Stylesheets, JavaScript files and so on.

Philosophy and Functionalities of Kigg

Let us discuss the philosophy of Digg/DotNetKicks kinds of applications before moving to the implementation part. Both of these applications are completely community driven. Members find an interesting Internet resource, they submit it to the application and it appears instantly in the upcoming story queue. Other members can vote it, and once it reaches a certain number of votes, it appears in the front page.

The application will be able to:

  • List all the published Stories.
  • List the Stories by Category.
  • List the Upcoming Stories.
  • List Stories by Tag.
  • List Stories Posted By an Individual User.
  • Search Stories.
  • View the Details of a Story.
  • Allow user to Submit Story (Requires Login)
  • Allow user to Kigg (Vote) Story (Requires Login)
  • Allow user to post comment on a Story (Requires Login)
  • Allow the Users to Login.
  • Allow the users to Signup
  • Allow the User to reset lost password.

Controllers and Actions Defined

  • The functionalities of Kigg are Story and User related. Therefore, we can categorize these functionalities into two Controllers:
  • StoryController: Handles all Story Listing, Searching, Submitting, Kigging etc.
  • UserController: Handles Authentication, Signup, Forgot Password etc.

When evaluating the Controller Actions, a good technique is to give the action a name similar to its functionality. The following code shows the action methods of the StoryController:

  1. public class StoryController  
  2. {  
  3.     //List published stories for all category or for a specific category  
  4.     [ControllerAction]  
  5.     public void Category(string name, int? page)  
  6.     {  
  7.     }  
  8.   
  9.     //List all upcoming stories regardless the category  
  10.     [ControllerAction]  
  11.     public void Upcoming(int? page)  
  12.     {  
  13.     }  
  14.   
  15.     //List Stories for a specific tag  
  16.     [ControllerAction]  
  17.     public void Tag(string name, int? page)  
  18.     {  
  19.     }  
  20.   
  21.     //List Stories Posted by a Specific User  
  22.     [ControllerAction]  
  23.     public void PostedBy(string name, int? page)  
  24.     {  
  25.     }  
  26.   
  27.     //Search the Stories  
  28.     [ControllerAction]  
  29.     public void Search(string q, int? page)  
  30.     {  
  31.     }  
  32.   
  33.     //View the details of a specific story  
  34.     [ControllerAction]  
  35.     public void Detail(int id)  
  36.     {  
  37.     }  
  38.   
  39.     //Submit a Story  
  40.     [ControllerAction]  
  41.     public void Submit(string storyUrl, string storyTitle, int storyCategoryId,   
  42.                        string storyDescription, string storyTags)  
  43.     {  
  44.     }  
  45.   
  46.     //Kigg the Story  
  47.     [ControllerAction]  
  48.     public void Kigg(int storyId)  
  49.     {  
  50.     }  
  51.   
  52.     //Post a Comment  
  53.     [ControllerAction]  
  54.     public void Comment(int storyId, string commentContent)  
  55.     {  
  56.     }  
  57. }  

And the following code snippet shows the action methods of UserController:

  1. public class UserController  
  2. {  
  3.     // Login  
  4.     [ControllerAction]  
  5.     public void Login(string userName, string password, bool rememberMe)  
  6.     {  
  7.     }  
  8.   
  9.     //Logout  
  10.     [ControllerAction]  
  11.     public void Logout()  
  12.     {  
  13.     }  
  14.   
  15.     // Reset the current password and mail back the new password  
  16.     [ControllerAction]  
  17.     public void SendPassword(string email)  
  18.     {  
  19.     }  
  20.   
  21.     //User Registration  
  22.     [ControllerAction]  
  23.     public void Signup(string userName, string password, string email)  
  24.     {  
  25.     }  
  26. }  

Note that the Action methods are declared as public and Marked with the ControllerAction attribute. In future versions of ASP.NET MVC the attribute will no longer be needed. Publicly declared method will automatically become the action methods.

Routing Rules Defined

Once the signatures of the Controllers are defined, it is time to declare the URL Routing Rules that will map the URLs to these Controllers' action methods. As mentioned earlier, the Routing Rules must be registered in the application start event handler, in the Global.asax file. When defining the Routing Rules, one thing you have to remember is keeping the most specific rule at the top. It is similar to the try/catch block rule of exception handling, where we put the more general exception at the bottom and specific exceptions at the top. If you open the Global.asax file of this application, you will find that we have explicitly created two methods to register these rules and call these methods in the application start event. The reasons behind creating these methods – instead of registering them in the application start event – are that we do not want to clutter our rules with both newer and older versions of IIS. When hosting an MVC application in IIS7 the URLs are extension-less, which is really cool, but in older versions of IIS the URLs are served with a .mvc extension. So to support both older and newer versions of IIS we need to add the same URL, one with the extension for the older version and another without extension for the newer version of IIS. Here, we are checking the web.config for the hosting IIS version and registering only the rules for that version. Another benefit of having explicit methods for registering the rules is Unit Testing, which we will check in a few moments. The following code shows the Routing Rules Declarations:

  1. protected void Application_Start(object sender, EventArgs e)  
  2. {  
  3.     RegisterRoutes(RouteTable.Routes);  
  4. } public static void RegisterRoutes(RouteCollection routes)  
  5. {  
  6.     int iisVersion = Convert.ToInt32(ConfigurationManager.AppSettings["IISVersion"]);  
  7.   
  8.     if (iisVersion >= 7)  
  9.     {  
  10.         RegisterRoutesForNewIIS(routes);  
  11.     }  
  12.     else  
  13.     {  
  14.         RegisterRoutesForOldIIS(routes);  
  15.     }  
  16. }   
  17. private static void RegisterRoutesForNewIIS(ICollection<Route> routes)  
  18. {  
  19.     var defaults = new  
  20.     {  
  21.         controller = "Story",  
  22.         action = "Category",  
  23.         name = (string)null,  
  24.         page = (int?)null  
  25.     };  
  26.   
  27.     routes.Add(  
  28.                     new Route  
  29.                     {  
  30.                         Url = "User/Login",  
  31.                         RouteHandler = typeof(MvcRouteHandler),  
  32.                         Defaults = new  
  33.                         {  
  34.                             controller = "User",  
  35.                             action = "Login"  
  36.                         }  
  37.                     }  
  38.                 );  
  39.   
  40.     routes.Add(  
  41.                     new Route  
  42.                     {  
  43.                         Url = "User/Logout",  
  44.                         RouteHandler = typeof(MvcRouteHandler),  
  45.                         Defaults = new  
  46.                         {  
  47.                             controller = "User",  
  48.                             action = "Logout"  
  49.                         }  
  50.                     }  
  51.                 );  
  52.   
  53.     routes.Add(  
  54.                     new Route  
  55.                     {  
  56.                         Url = "User/Signup",  
  57.                         RouteHandler = typeof(MvcRouteHandler),  
  58.                         Defaults = new  
  59.                         {  
  60.                             controller = "User",  
  61.                             action = "Signup"  
  62.                         }  
  63.                     }  
  64.                 );  
  65.   
  66.     routes.Add(  
  67.                     new Route  
  68.                     {  
  69.                         Url = "User/SendPassword",  
  70.                         RouteHandler = typeof(MvcRouteHandler),  
  71.                         Defaults = new  
  72.                         {  
  73.                             controller = "User",  
  74.                             action = "SendPassword"  
  75.                         }  
  76.                     }  
  77.                 );  
  78.   
  79.     routes.Add(  
  80.                     new Route  
  81.                     {  
  82.                         Url = "Story/Detail/[id]",  
  83.                         RouteHandler = typeof(MvcRouteHandler),  
  84.                         Defaults = new  
  85.                         {  
  86.                             controller = "Story",  
  87.                             action = "Detail"  
  88.                         }  
  89.                     }  
  90.                 );  
  91.   
  92.     routes.Add(  
  93.                     new Route  
  94.                     {  
  95.                         Url = "Story/Upcoming/[page]",  
  96.                         RouteHandler = typeof(MvcRouteHandler),  
  97.                         Defaults = new  
  98.                         {  
  99.                             controller = "Story",  
  100.                             action = "Upcoming"  
  101.                         }  
  102.                     }  
  103.                 );  
  104.   
  105.     routes.Add(  
  106.                     new Route  
  107.                     {  
  108.                         Url = "Story/Search/[q]/[page]",  
  109.                         RouteHandler = typeof(MvcRouteHandler),  
  110.                         Defaults = new  
  111.                         {  
  112.                             controller = "Story",  
  113.                             action = "Search"  
  114.                         }  
  115.                     }  
  116.                 );  
  117.   
  118.     routes.Add(  
  119.                     new Route  
  120.                     {  
  121.                         Url = "Story/Category/[page]",  
  122.                         RouteHandler = typeof(MvcRouteHandler),  
  123.                         Defaults = defaults  
  124.                     }  
  125.                 );  
  126.   
  127.     routes.Add(  
  128.                     new Route  
  129.                     {  
  130.                         Url = "Story/[action]/[name]/[page]",  
  131.                         RouteHandler = typeof(MvcRouteHandler),  
  132.                         Defaults = defaults  
  133.                     }  
  134.                 );  
  135.   
  136.     routes.Add(  
  137.                     new Route  
  138.   
  139.                     {  
  140.                         Url = "[controller]/[action]/[id]",  
  141.                         RouteHandler = typeof(MvcRouteHandler),  
  142.                         Defaults = defaults  
  143.                     }  
  144.                 );  
  145.   
  146.     routes.Add(  
  147.                     new Route  
  148.                     {  
  149.                         Url = "Default.aspx",  
  150.                         RouteHandler = typeof(MvcRouteHandler),  
  151.                         Defaults = defaults  
  152.                     }  
  153.                 );  
  154. }  

As you can see, we are registering the most specific rules like User/Login, User/Signup, Story/Detail, Story/Category first and least specific rules like Story/[action], [controller]/[action] later. When mentioning the variables in the URL format we are using [] to denote the variable. There are two fixed variable names in the MVC framework: [controller] and [action]. The others are simply the parameter names of the controller action methods. The last rule, which maps to default.aspx to the all category is required to handle the /.

Test the Routing Rules

Once the Routing Rules and Controller signatures are defined, it's time to test the Rules, which will give us a clear idea on whether the Rules are good enough to map the controller's action and if the correct parameter values are passed for a given URL. The following table shows the test bed of Routing rules that we like to test:

Table 1: Tests

Functionality Url Format Controller Action
Login User/Login UserController Login
SendPassword User/SendPassword UserController SendPassword
Signup User/Signup UserController Signup
Logout User/Logout UserController Logout
List All Published Story Story/Category
Story/Category/[page]
StoryController Category
List Published Stories for a specific Category Story/Category/[categoryName]
Story/Category/[categoryName]/[page]
StoryController Category
List Upcoming Stories Story/Upcoming
Story/Upcoming/[page]
StoryController Upcoming
List Stories for a specific Tag Story/Tag/[tagName]
Story/Tag/[tagName]/[page]
StoryController Tag
List Stories Posted By an User Story/PostedBy/[userName]
Story/PostedBy/[userName]/[page]
StoryController PostedBy
Search Stories Story/Search?q=query
Story/Search/[q]/[page]
StoryController Search
View Details of a Story Story/Detail/[storyID] StoryController Detail
Submit a Story Story/Submit StoryController Submit
Vote a Story Story/Kigg StoryController Kigg
Post a Comment Story/Comment StoryController Comment

You will find Route.cs in the test projects folder, which is used to test the Routes. We have included both VSTSTest and NUnit implementations of all the Unit Tests. For Mocking, we have used Rhino Mocks. The tests are done based upon the code that Phil Haack blogged few weeks ago in the post Testing Routes In ASP.NET MVC.

Here are few code snippets of the Routing Rule Test:

  1. [TestInitialize]  
  2. public void Init()  
  3. {  
  4.     routes = new RouteCollection();  
  5.     Global.RegisterRoutes(routes);  
  6.   
  7.     mocks = new MockRepository();  
  8. }  
  9.   
  10. [TestMethod]  
  11. public void VerifyDefault()  
  12. {  
  13.     IHttpContext httpContext;  
  14.   
  15.     using (mocks.Record())  
  16.     {  
  17.         httpContext = GetHttpContext(mocks, "~/Default.aspx");  
  18.     }  
  19.   
  20.     using (mocks.Playback())  
  21.     {  
  22.         RouteData routeData = routes.GetRouteData(httpContext);  
  23.   
  24.         Assert.IsNotNull(routeData);  
  25.         Assert.AreEqual("Story", routeData.Values["Controller"]);  
  26.         Assert.AreEqual("Category", routeData.Values["action"]);  
  27.     }  
  28. }  
  29.   
  30. [TestMethod]  
  31. public void VerifyAllCategory()  
  32. {  
  33.     IHttpContext httpContext;  
  34.   
  35.     using (mocks.Record())  
  36.     {  
  37.         httpContext = GetHttpContext(mocks, "~/Story/Category/20");  
  38.     }  
  39.   
  40.     using (mocks.Playback())  
  41.     {  
  42.         RouteData routeData = routes.GetRouteData(httpContext);  
  43.   
  44.         Assert.IsNotNull(routeData);  
  45.         Assert.AreEqual("Story", routeData.Values["Controller"]);  
  46.         Assert.AreEqual("Category", routeData.Values["action"]);  
  47.         Assert.IsNull(routeData.Values["name"]);  
  48.         Assert.AreEqual("20", routeData.Values["page"]);  
  49.     }  
  50. }  

Implementing the UserController

Earlier, we defined the signature of the UserController. Now it's time to add the actual implementation. The UserController uses the ASP.NET Membership provider for login, signup and similar operations. The only difference between this controller and the others is that all the methods of this controller returns JSON data instead of a regular HTML view. The controller methods are called from the client side using the ASP.NET AJAX Framework. The following code shows the Login method of this controller:

  1. [ControllerAction]  
  2. public void Login(string userName, string password, bool rememberMe)  
  3. {  
  4.     using (new CodeBenchmark())  
  5.     {  
  6.         JsonResult result = new JsonResult();  
  7.   
  8.         if (string.IsNullOrEmpty(userName))  
  9.         {  
  10.             result.errorMessage = "User name cannot be blank.";  
  11.         }  
  12.         else if (string.IsNullOrEmpty(password))  
  13.         {  
  14.             result.errorMessage = "Password cannot be blank.";  
  15.         }  
  16.         else if (!UserManager.ValidateUser(userName, password))  
  17.         {  
  18.             result.errorMessage = "Invalid login credentials.";  
  19.         }  
  20.         else  
  21.         {  
  22.             //The following check is required for TDD   
  23.             if (HttpContext != null)  
  24.             {  
  25.                 FormsAuthentication.SetAuthCookie(userName, rememberMe);  
  26.             }  
  27.   
  28.             result.isSuccessful = true;  
  29.         }  
  30.   
  31.         RenderView("Json", result);  
  32.     }  
  33. }  

As you can see, we are creating a JsonResult object at the beginning of the method. JsonResult is a simple class which has only two properties: isSuccessful and errorMessage, which denotes if the controller action was successful. If the operation is not successful, it populates the errorMessage with the proper reason. In the end, we are passing the result as view data of the shared view called "Json". Since this is a shared view and it will be used by both UserController and StoryController, we have placed it in the Shared folder under the Views. I am excluding the other methods of this controller as they work almost the same as this. One important thing I would like to mention is that instead of using the static Membership class directly in the code, we are passing the abstract membership provider in the constructor of this controller. The reason behind this is that we can pass a Mock Membership Provider in our Unit Test, which we will check next. And in the other constructor, we are passing the default membership provider, which is configured in the web.config.

Testing the UserController

For testing the Controller, we are also following the same test specific subclass pattern that Phil Haack blogged few weeks ago in the post Writing Unit Tests For Controller Actions. As mentioned in the previous section, we will pass a Mock Membership Provider to test the controller. We will expect that the proper method of the membership provider is called and that the controller is passing correct data to the view. The following code snippet shows how the Login is performed for a successful attempt and how it fails when an empty user name is provided.

  1. [TestInitialize]  
  2. public void Init()  
  3. {  
  4.     mocks = new MockRepository();  
  5.     userManager = mocks.PartialMock<MembershipProvider>();  
  6.     controller = new UserControllerForTest(userManager);  
  7. }  
  8.   
  9. [TestMethod]  
  10. public void ShouldLogin()  
  11. {  
  12.     using(mocks.Record())  
  13.     {  
  14.         Expect.Call(userManager.ValidateUser(DefaultUserName, DefaultPassword)).IgnoreArguments().Return(true);  
  15.     }  
  16.   
  17.     using(mocks.Playback())  
  18.     {  
  19.   
  20.         controller.Login(DefaultUserName, DefaultPassword, true);  
  21.     }  
  22.   
  23.     Assert.AreEqual(controller.SelectedView, "Json");  
  24.     Assert.IsInstanceOfType(controller.SelectedViewData, typeof(JsonResult));  
  25.     Assert.IsTrue(((JsonResult)controller.SelectedViewData).isSuccessful);  
  26.     Assert.IsNull(((JsonResult)controller.SelectedViewData).errorMessage);  
  27. }  
  28.   
  29. [TestMethod]  
  30. public void ShoudNotLoginForEmptyUserName()  
  31. {  
  32.     controller.Login(string.Empty, DefaultPassword, false);  
  33.   
  34.     Assert.AreEqual(controller.SelectedView, "Json");  
  35.     Assert.IsInstanceOfType(controller.SelectedViewData, typeof(JsonResult));  
  36.     Assert.IsFalse(((JsonResult)controller.SelectedViewData).isSuccessful);  
  37.     Assert.AreEqual(((JsonResult)controller.SelectedViewData).errorMessage, "User name cannot be blank.");  
  38. }  

Summary

My initial intention was to provide all the details in a single article, but as you can see, the article has already evolved too much to fit in a single part.

In this part, we took a brief overview of the ASP.NET MVC Framework and learned how to assign functionality to controllers, how to define the routing rules and test them against URLs. We have also seen how to create a Controller that returns JSON data instead of regular HTML, and tested the controller. In the next part, we will talk more about the Controller, which renders regular HTML view, using master pages and user controls to create the view, passing strongly typed view data to the view and finally creating the Model. Stay tuned.

About Kazi Manzur Rashid

Kazi Manzur Rashid, Nickname Amit is a Diehard fan of Microsoft Technology. He started programming with Visual Basic 5.0 back in 1996. Since then he has developed many diversified solutions in his professional career, which spans from Anti-Spyware Tool to Personalized Start Page. Currently He is wor…

View complete profile

[Google]Google阅读器新功能:帮你整理数据

mikel阅读(694)

随时了解多个RSS更新是很困难的,它太花费时间和精力,不过Google近日推出了一种新的方法来整理这些数据,包括你订阅的博客何时发表了新帖子以及你什么时候阅读了它们。

  通过简单的柱状图Google阅读器将一些统计数据明了地展示出来,蓝色数据条表示了文章的发表时间,红色数据条代表了你已阅读的文章。你可以查看周数据也可以查看月数据,结果一定令你很满意。

  该系统目前还不能在多组网站间工作,只能统计单个博客的更新,点击“显示详细信息”,会有一个下拉窗口显示该文章的部分内容以及阅读次数。相信在不久的将来它就可以实现在多个群组间工作,到那时我们的阅读就更加方便了。

Google阅读器新功能:帮你整理资料

[网站]PlentyOfFish架构学习

mikel阅读(880)

这个叫PlentyOfFish网站,  它只有一个员工,每天只干两小时活,但是却被估值$1000000000

PlentyOfFish在山姆大叔家里算是一个很火的online dating网站,网站取名字来自这句话:There is plenty of fish in the sea. 对应的中文意思是天涯何处无芳草 Fish的意境虽然差了一点,但是却更加形象地表达了遍地撒网,中心钓鱼的泡妞的法则。 PlentyOfFish每个月有4500多万的访问用户,每天3000多万点击率。这个流量实际上并不夸张,平均算下来大概也就是500-600 request/s,峰值可能会更加高一点。在国内,我们可以发现douban.com就和它有的一拼,流量可能也差不了多少了。但是网站价值差多少倍偶就不知道了。通过和一般的互联网公司比较,大家可能认为这么流量也不小的公司怎么也要上百人左右,但它的确是只有一个干活的,这人就是Markus Frind。还有个非正式的好像是他女朋友,有时候帮他回回用户来信。当网站每个月能够收入4000块钱时候这个家伙就辞掉了全职工作,开始学着做SEO和竞价排名等等了。当我第一次访问这个网站的时候,第一感觉就是这也太土了把。但是或许正是因为它的土,它的默默无闻,使得没有人愿意去抄袭,去hack它。但有点我们得承认,Markus在网站内容和广告方面的确处理的很好,的确有不少值得我们学习的地方。

PlentyOfFish作为一个交友网站,我们或许并没有发现它有什么特殊的地方,也没有发现让人想尖叫的feature,但或许真是细节往往决定成败。它的成功的关键因素可能就是在基本功能方面能很符合用户的需要,在UE方面做的也比较贴心,同时也让用户能够坦然接受这个免费网站的UI的丑陋和服务的不稳定性,而更为愿意通过这个平台来发布一些内容,share一些个人图片,通过这个网站来找靓妞或者帅哥dating了。 PlentyOfFish允许它的用户上传很多照片,据统计已达到了6TB,然后允许每个用户可以对其它用户的照片进行打分,这中间的交互过程可能是给了用户带来很多自我表达的机会,使得他们开始沉迷于这个网站。PlentyOfFish在用户资料真实性方面做了很多工作,提供了各种举报和打假功能,估计站长每天就是在删除这些捣乱用户了。然而它的流行并不能阻止我们对那个1后面加很多0的估值的疑问?它凭什么值那么多美元?PlentyOfFish并没有收取任何注册用户的费用,而是宣称100% Free。那他到底怎么挣钱的呢?难道他有什么绝佳的盈利模式idea?实际上和我们很多人一样,就是仅仅靠网站打点广告,收点广告费。而我们加上阿里妈妈广告条,每个月只能给自己增加点零花钱,但是他现在一年可以从google Adsense混到1000万美元还多。TMD,眼红,口水直流…..而且这个数据应该已经过时了。一个站长能把一个网站推到如此规模,我不知道这个星球上还有没有一个可以和他比肩的人。有的话你让他站出来,偶马上跪拜在地……师傅,教我J……

Markus,他和我一样,不是我和他,呵呵,只会使用.Net,所以想做个网站,也只好从用ASP.NET开始了。正是这个网站的简单想法,使得一个具有传奇意义的互联网产品诞生了。至于Markus当时怎么bootstrap这个网站,以及怎么一步一步做大,我就不得而知了。PlentyOfFish的后台采用的技术和产品可能和我们大部分使用.Net的网站一样,下面我们就来一起简单分析一下它的后台架构。

·         Web框架和服务器:  ASP.NET+IIS+ Windows x64 Server 2003

·         Web Server 2 个做负载均衡的web servers , 基本配置是 2 Quad Core Intel    Xeon X5355 @ 2.66Ghz), 8 G RAM (使用了大约800 MBs), 2 hard drives

 

·         DB Server:  3SQL Server,采用master-slave架构,两台负责read操作,master那台负责写操作。这个和myspace早期的后台数据库架构是一样,看来这种架构很流行嘛。

·         Internet connection 1Gbps 使用了大概200Mbps

·         CDN: 使用了Akamai的服务,每天要传输1.7亿张图片

·         图片规模:6TB左右,数亿张图片

·         资源状态:Everything is dynamic. Nothing is static。没有使用ASP.NET内部cache机制,资源很快就Expire了。

·         压缩: 所有的request数据都使用了gzip压缩,大概耗费了30%CPU,但是降低了带宽成本。欧美的带宽不便宜。

·         Load Balance 使用了ServerIron ,而不是NLB(windows负载均衡解决方案) ServerIron NLB便宜而且简单很多。

·         Performance Monitor: Windows Task Manager,够牛

·         I/O操作: 因为内容很难cache,所以花了很多功夫在db performance tuning上。

·         MemoryCPU: 把最近常使用的图片直接放在内存中,所以内存会那么大;CPU配置也挺好,gzip是相当耗费CPU计算的。

Markus说他碰到问题基本上是IO操作方面的瓶颈,很少是被.Net block住。MarkusSessionFarm,以及数据库反范式等很多方面都有很不错的经验,很值得我们学习和借鉴,更多的细节大家可以参考后面的链接的几篇文章。

Markus最近也有了新的计划,他做了一些让POF进一步发展的计划:

·         盈利模式发生一些改变,不仅仅依靠google ads。希望从所有用户身上榨取更多的美元出来。

·         打算打造一个营销团队

·         观察竞争对手然后加入新的功能

·         考虑使用Amazon S3存储来达到地域分布式load balanced。就像建立多个data centers一样。主要可能是S3价格相当便宜。


作者:shawnliu

出处:http://www.cnblogs.com/liushouzhao

 

参考文章

PlentyOfFish Architecture

PlentyOfFish 网站架构学习

About PlentyofFish

[网站]PlentyOfFish网站架构学习

mikel阅读(708)

采取 Windows 技术路线的 Web 2.0 站点并不多,除了 MySpace ,另外就是这个 PlentyOfFish。这个站点提供 "Online Dating” 服务。一个令人津津乐道的、惊人的数据是这个只有一个人(创建人Markus Frind)的站点价值 10 亿,估计要让很多人眼热,更何况 Markus Frind 每天只用两个小时打理网站–可操作性很强嘛。

之所以选择 Windows .NET 的技术路线是因为 Markus Frind 不懂 LAMP 那一套东西,会啥用啥。就这样,也能支撑 超过 3000 万的日点击率(从这个数字也能看出来人类对自然天性的渴望是多迫切)。Todd Hoff 收集了很多关于 PlentyOfFish 架构的细节。记录一下感兴趣的部分。

带宽与CPU

PlentyOfFish 比较特殊的一个地方是 几乎不需要 Cache,因为数据变化过快,很快就过期。我不知道这是因为 ASP.NET 的特点带来的架构特点,还是业务就是这个样子的。至于图片,则是通过 CDN 支撑的。对于动态出站(outbound)的数据进行压缩,这耗费了 30% 的 CPU 能力,但节省了带宽资源。我最近才知道,欧美的带宽开销也不便宜。

负载均衡

微软 Windows 网络负载均衡(Network Load Balancing) 的一个缺陷是不能保持 Session 状态(我没有用过这玩意儿,不能确认),价格也不便宜,而且复杂;网络负载均衡对 Windows 架构的站点又是必须–IIS 的总连接数是有限制的。PlentyOfFish 用的是 ServerIron (Conf Refer),ServerIron 使用简单,而且功能比 NLB 更丰富。

数据库

一共三台 SQL Server,一台作为主库,另外两台只读数据库支撑查询。数据库性能监控用的是“Windows 任务管理器"。因为 Cache没啥用,所以要花大力气优化 DB。每个页面上调用 DB 次数越少越好,越简单越好,这是常识,不过不是每个人都体会那么深而已。

微软好不容易找到了一个宣传案例,所以在 Channel 9 上有一个 PlentyOfFish 的访谈

PlentyOfFish 取自天涯何处无芳草(Plenty of fish in the sea)的意思,还挺有文化的。从这一点上看,比国内那些拉皮条的网站好一些。

[C#]读写二进制文件

mikel阅读(955)

  1using System;
  2using System.Drawing;
  3using System.Collections;
  4using System.ComponentModel;
  5using System.Windows.Forms;
  6using System.Data;
  7// 添加新的命名空间。
  8using System.IO;
  9using System.Text;
 10
 11namespace BinaryFile
 12{
 13    /// <summary>
 14    /// 处理二进制文件。
 15    /// </summary>

 16    public class Form1 : System.Windows.Forms.Form
 17    {
 18        private System.Windows.Forms.Button button1;
 19        private System.Windows.Forms.RichTextBox richTextBox1;
 20        /// <summary>
 21        /// 必需的设计器变量。
 22        /// </summary>

 23        private System.ComponentModel.Container components = null;
 24
 25        public Form1()
 26        {
 27            // Windows 窗体设计器支持所必需的
 28            InitializeComponent();
 29            // TODO: 在 InitializeComponent 调用后添加任何构造函数代码
 30            // 写二进制文件
 31            s = File.Create("test.bin");
 32            w = new BinaryWriter(s);
 33            string str = "这是一行文字。\n";
 34            w.Write(str);
 35            float a = 3.1415F;
 36            w.Write(a);
 37            ulong b = 100000L;
 38            w.Write(b);
 39            int c = 300;
 40            w.Write(c);
 41            decimal d = 4.40983M;
 42            w.Write(d);
 43            double f = 94853.938485928d;
 44            w.Write(f);
 45            char[] g = {'h','e','l','l','o'};
 46            w.Write(g, 0, g.Length);
 47            char h = 'W';
 48            w.Write(h);
 49            bool i = true;
 50            w.Write(i);
 51            w.Flush();
 52            w.Close();
 53        }

 54
 55        /// <summary>
 56        /// 清理所有正在使用的资源。
 57        /// </summary>

 58        protected override void Dispose( bool disposing )
 59        {
 60            if( disposing )
 61            {
 62                if (components != null
 63                {
 64                    components.Dispose();
 65                }

 66            }

 67            base.Dispose( disposing );
 68        }

 69
 70        Windows Form Designer generated code
116
117        /// <summary>
118        /// 应用程序的主入口点。
119        /// </summary>

120        [STAThread]
121        static void Main() 
122        {
123            Application.Run(new Form1());
124        }

125        // 定义私有变量
126        public Stream s;
127        // 读二进制文件
128        public BinaryReader r;
129        // 写二进制文件
130        public BinaryWriter w;
131        // 显示二进制文件内容
132        private void button1_Click(object sender, System.EventArgs e)
133        {
134            s = File.OpenRead("test.bin");
135            r = new BinaryReader(s);
136            richTextBox1.Text  = "显示 String:";
137            richTextBox1.Text +=  r.ReadString();
138            richTextBox1.Text += "显示 Float:";
139            richTextBox1.Text += r.ReadSingle().ToString() + "\n";
140            richTextBox1.Text += "显示 ULong:";
141            richTextBox1.Text += r.ReadUInt64().ToString() + "\n";
142            richTextBox1.Text += "显示 Int:";
143            richTextBox1.Text += r.ReadInt32().ToString() + "\n";
144            richTextBox1.Text += "显示 Decimal:";
145            richTextBox1.Text += r.ReadDecimal().ToString() + "\n";
146            richTextBox1.Text += "显示 Double:";
147            richTextBox1.Text += r.ReadDouble().ToString() + "\n";
148            richTextBox1.Text += "显示 Char[]:";
149            richTextBox1.Text += Encoding.ASCII.GetString(r.ReadBytes(5)) + "\n";
150            richTextBox1.Text += "显示 Char:";
151            richTextBox1.Text += r.ReadChar().ToString() + "\n";
152            richTextBox1.Text += "显示 Boolean:";
153            richTextBox1.Text += r.ReadBoolean().ToString() + "\n";
154            r.Close();
155        }

156    }

157}

[JQuery]JScript读写二进制文件

mikel阅读(1000)

asp环境中,vbscript先天性的在读写二进制数据方面比JScript方便,JScript本身并无针对二进制数据(或者说字节数据)的数 据类型、对象、方法,所以有些高手爱用JScript写ASP,但是如果涉及字节数据处理基本上都要头痛。我在CodeProject.com上看到这篇 文章,觉得非常有用,所以转过来。基本原理就是用ADODB.Stream读写时指定为437代码页就可以以文本的形式来读写了。
原文网址: http://www.codeproject.com/KB/scripting/Exsead7.aspx

Reading And Writing Binary Files Using JScript

 

 By alex turner

An easy to use class that gives some binary file abilities to JScript

JavaScript, Windows, Visual Studio, Dev
Posted: 1 Mar 2007
Updated: 1 Mar 2007
Views: 9,885
Bookmarked: 5 times

 

Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

As I have pushed what I can do in JScript, I have come up against the issue of binary files. The following class solved this for small to medium size files. I am partly including this post here because I am about to put in a post about sending emails with attachments via JScript and it will use this binary file code to read in the binary attachment files.

So, here is a nice way of accessing binary files from JScript. If you want to access this object from VBScript, then put it in a swf file. This object has been used in anger to upload files to a web service and has proven to be nice and fast for files in the 0-10 MByte region.

 

/** This is a fairly well optimized object which alows
  * access to binary files from JScript on a Windows
  * operating system.
  *
  * A the end of the file is small set of tests to show how it
  * is used.  You will require ADODB 2.5 or higher installed.
  * this will be so on most 2000 machines and all XP or higher
  * machines.
  *
  * CopyRight: Dr Alexander J Turner – all rights reserved.
  * Please feel free to use this code in any way you like
  * as long as you place a reference in the comments that
  * I wrote it.
  
*/
function BinaryFile(name)
{
    
var adTypeBinary = 1 
    
var adTypeText   = 2 
    
var adSaveCreateOverWrite = 2
    
// The trick – this is the 'old fassioned' not translation page

    
// It lest JavaScript use strings to act like raw octets

    
var codePage='437';
   
    
this.path=name;
   
    
var forward  = new Array();
    
var backward = new Array();
   
    
// Note – for better performance I should preconvert these hex

    
// definitions to decimal – at some point 🙂 – AJT

    forward[
'80'= '00C7';
    forward[
'81'= '00FC';
    forward[
'82'= '00E9';
    forward[
'83'= '00E2';
    forward[
'84'= '00E4';
    forward[
'85'= '00E0';
    forward[
'86'= '00E5';
    forward[
'87'= '00E7';
    forward[
'88'= '00EA';
    forward[
'89'= '00EB';
    forward[
'8A'= '00E8';
    forward[
'8B'= '00EF';
    forward[
'8C'= '00EE';
    forward[
'8D'= '00EC';
    forward[
'8E'= '00C4';
    forward[
'8F'= '00C5';
    forward[
'90'= '00C9';
    forward[
'91'= '00E6';
    forward[
'92'= '00C6';
    forward[
'93'= '00F4';
    forward[
'94'= '00F6';
    forward[
'95'= '00F2';
    forward[
'96'= '00FB';
    forward[
'97'= '00F9';
    forward[
'98'= '00FF';
    forward[
'99'= '00D6';
    forward[
'9A'= '00DC';
    forward[
'9B'= '00A2';
    forward[
'9C'= '00A3';
    forward[
'9D'= '00A5';
    forward[
'9E'= '20A7';
    forward[
'9F'= '0192';
    forward[
'A0'= '00E1';
    forward[
'A1'= '00ED';
    forward[
'A2'= '00F3';
    forward[
'A3'= '00FA';
    forward[
'A4'= '00F1';
    forward[
'A5'= '00D1';
    forward[
'A6'= '00AA';
    forward[
'A7'= '00BA';
    forward[
'A8'= '00BF';
    forward[
'A9'= '2310';
    forward[
'AA'= '00AC';
    forward[
'AB'= '00BD';
    forward[
'AC'= '00BC';
    forward[
'AD'= '00A1';
    forward[
'AE'= '00AB';
    forward[
'AF'= '00BB';
    forward[
'B0'= '2591';
    forward[
'B1'= '2592';
    forward[
'B2'= '2593';
    forward[
'B3'= '2502';
    forward[
'B4'= '2524';
    forward[
'B5'= '2561';
    forward[
'B6'= '2562';
    forward[
'B7'= '2556';
    forward[
'B8'= '2555';
    forward[
'B9'= '2563';
    forward[
'BA'= '2551';
    forward[
'BB'= '2557';
    forward[
'BC'= '255D';
    forward[
'BD'= '255C';
    forward[
'BE'= '255B';
    forward[
'BF'= '2510';
    forward[
'C0'= '2514';
    forward[
'C1'= '2534';
    forward[
'C2'= '252C';
    forward[
'C3'= '251C';
    forward[
'C4'= '2500';
    forward[
'C5'= '253C';
    forward[
'C6'= '255E';
    forward[
'C7'= '255F';
    forward[
'C8'= '255A';
    forward[
'C9'= '2554';
    forward[
'CA'= '2569';
    forward[
'CB'= '2566';
    forward[
'CC'= '2560';
    forward[
'CD'= '2550';
    forward[
'CE'= '256C';
    forward[
'CF'= '2567';
    forward[
'D0'= '2568';
    forward[
'D1'= '2564';
    forward[
'D2'= '2565';
    forward[
'D3'= '2559';
    forward[
'D4'= '2558';
    forward[
'D5'= '2552';
    forward[
'D6'= '2553';
    forward[
'D7'= '256B';
    forward[
'D8'= '256A';
    forward[
'D9'= '2518';
    forward[
'DA'= '250C';
    forward[
'DB'= '2588';
    forward[
'DC'= '2584';
    forward[
'DD'= '258C';
    forward[
'DE'= '2590';
    forward[
'DF'= '2580';
    forward[
'E0'= '03B1';
    forward[
'E1'= '00DF';
    forward[
'E2'= '0393';
    forward[
'E3'= '03C0';
    forward[
'E4'= '03A3';
    forward[
'E5'= '03C3';
    forward[
'E6'= '00B5';
    forward[
'E7'= '03C4';
    forward[
'E8'= '03A6';
    forward[
'E9'= '0398';
    forward[
'EA'= '03A9';
    forward[
'EB'= '03B4';
    forward[
'EC'= '221E';
    forward[
'ED'= '03C6';
    forward[
'EE'= '03B5';
    forward[
'EF'= '2229';
    forward[
'F0'= '2261';
    forward[
'F1'= '00B1';
    forward[
'F2'= '2265';
    forward[
'F3'= '2264';
    forward[
'F4'= '2320';
    forward[
'F5'= '2321';
    forward[
'F6'= '00F7';
    forward[
'F7'= '2248';
    forward[
'F8'= '00B0';
    forward[
'F9'= '2219';
    forward[
'FA'= '00B7';
    forward[
'FB'= '221A';
    forward[
'FC'= '207F';
    forward[
'FD'= '00B2';
    forward[
'FE'= '25A0';
    forward[
'FF'= '00A0';
    backward[
'C7']   = '80';
    backward[
'FC']   = '81';
    backward[
'E9']   = '82';
    backward[
'E2']   = '83';
    backward[
'E4']   = '84';
    backward[
'E0']   = '85';
    backward[
'E5']   = '86';
    backward[
'E7']   = '87';
    backward[
'EA']   = '88';
    backward[
'EB']   = '89';
    backward[
'E8']   = '8A';
    backward[
'EF']   = '8B';
    backward[
'EE']   = '8C';
    backward[
'EC']   = '8D';
    backward[
'C4']   = '8E';
    backward[
'C5']   = '8F';
    backward[
'C9']   = '90';
    backward[
'E6']   = '91';
    backward[
'C6']   = '92';
    backward[
'F4']   = '93';
    backward[
'F6']   = '94';
    backward[
'F2']   = '95';
    backward[
'FB']   = '96';
    backward[
'F9']   = '97';
    backward[
'FF']   = '98';
    backward[
'D6']   = '99';
    backward[
'DC']   = '9A';
    backward[
'A2']   = '9B';
    backward[
'A3']   = '9C';
    backward[
'A5']   = '9D';
    backward[
'20A7'= '9E';
    backward[
'192']  = '9F';
    backward[
'E1']   = 'A0';
    backward[
'ED']   = 'A1';
    backward[
'F3']   = 'A2';
    backward[
'FA']   = 'A3';
    backward[
'F1']   = 'A4';
    backward[
'D1']   = 'A5';
    backward[
'AA']   = 'A6';
    backward[
'BA']   = 'A7';
    backward[
'BF']   = 'A8';
    backward[
'2310'= 'A9';
    backward[
'AC']   = 'AA';
    backward[
'BD']   = 'AB';
    backward[
'BC']   = 'AC';
    backward[
'A1']   = 'AD';
    backward[
'AB']   = 'AE';
    backward[
'BB']   = 'AF';
    backward[
'2591'= 'B0';
    backward[
'2592'= 'B1';
    backward[
'2593'= 'B2';
    backward[
'2502'= 'B3';
    backward[
'2524'= 'B4';
    backward[
'2561'= 'B5';
    backward[
'2562'= 'B6';
    backward[
'2556'= 'B7';
    backward[
'2555'= 'B8';
    backward[
'2563'= 'B9';
    backward[
'2551'= 'BA';
    backward[
'2557'= 'BB';
    backward[
'255D'= 'BC';
    backward[
'255C'= 'BD';
    backward[
'255B'= 'BE';
    backward[
'2510'= 'BF';
    backward[
'2514'= 'C0';
    backward[
'2534'= 'C1';
    backward[
'252C'= 'C2';
    backward[
'251C'= 'C3';          
    backward[
'2500'= 'C4';
    backward[
'253C'= 'C5';
    backward[
'255E'= 'C6';
    backward[
'255F'= 'C7';
    backward[
'255A'= 'C8';
    backward[
'2554'= 'C9';
    backward[
'2569'= 'CA';
    backward[
'2566'= 'CB';
    backward[
'2560'= 'CC';
    backward[
'2550'= 'CD';
    backward[
'256C'= 'CE';
    backward[
'2567'= 'CF';
    backward[
'2568'= 'D0';
    backward[
'2564'= 'D1';
    backward[
'2565'= 'D2';
    backward[
'2559'= 'D3';
    backward[
'2558'= 'D4';
    backward[
'2552'= 'D5';
    backward[
'2553'= 'D6';
    backward[
'256B'= 'D7';
    backward[
'256A'= 'D8';
    backward[
'2518'= 'D9';
    backward[
'250C'= 'DA';
    backward[
'2588'= 'DB';
    backward[
'2584'= 'DC';
    backward[
'258C'= 'DD';
    backward[
'2590'= 'DE';
    backward[
'2580'= 'DF';
    backward[
'3B1']  = 'E0';
    backward[
'DF']   = 'E1';
    backward[
'393']  = 'E2';
    backward[
'3C0']  = 'E3';
    backward[
'3A3']  = 'E4';
    backward[
'3C3']  = 'E5';
    backward[
'B5']   = 'E6';
    backward[
'3C4']  = 'E7';
    backward[
'3A6']  = 'E8';
    backward[
'398']  = 'E9';
    backward[
'3A9']  = 'EA';
    backward[
'3B4']  = 'EB';                                                              
    backward[
'221E'= 'EC';
    backward[
'3C6']  = 'ED';
    backward[
'3B5']  = 'EE';
    backward[
'2229'= 'EF';
    backward[
'2261'= 'F0';
    backward[
'B1']   = 'F1';
    backward[
'2265'= 'F2';
    backward[
'2264'= 'F3';
    backward[
'2320'= 'F4';
    backward[
'2321'= 'F5';
    backward[
'F7']   = 'F6';
    backward[
'2248'= 'F7';
    backward[
'B0']   = 'F8';
    backward[
'2219'= 'F9';
    backward[
'B7']   = 'FA';
    backward[
'221A'= 'FB';
    backward[
'207F'= 'FC';
    backward[
'B2']   = 'FD';
    backward[
'25A0'= 'FE';
    backward[
'A0']   = 'FF';     
   
    
var hD="0123456789ABCDEF";
    
this.d2h = function(d)
    {
        
var h = hD.substr(d&15,1);
        
while(d>15) {d>>=4;h=hD.substr(d&15,1)+h;}
        
return h;
    }
    
this.h2d = function(h)
    {
        
return parseInt(h,16);
    }
   
    
this.WriteAll = function(what)
    {
        
//Create Stream object

        
var BinaryStream = WScript.CreateObject("ADODB.Stream");
        
//Specify stream type – we cheat and get string but 'like' binary

        BinaryStream.Type 
= adTypeText;
        BinaryStream.CharSet 
= '437';         
        
//Open the stream

        BinaryStream.Open();
        
// Write to the stream

        BinaryStream.WriteText(
this.Forward437(what));
        
// Write the string to the disk

        BinaryStream.SaveToFile(
this.path, adSaveCreateOverWrite);
        
// Clearn up

        BinaryStream.Close();
    }
   
    
this.ReadAll  = function()
    {
        
//Create Stream object – needs ADO 2.5 or heigher

        
var BinaryStream = WScript.CreateObject("ADODB.Stream")
        
//Specify stream type – we cheat and get string but 'like' binary

        BinaryStream.Type 
= adTypeText;
        BinaryStream.CharSet 
= codePage;
        
//Open the stream

        BinaryStream.Open();
        
//Load the file data from disk To stream object

        BinaryStream.LoadFromFile(
this.path);
        
//Open the stream And get binary 'string' from the object

        
var what = BinaryStream.ReadText;
        
// Clean up

        BinaryStream.Close();
        
return this.Backward437(what);
    }
   
    
/* Convert a octet number to a code page 437 char code */
    
this.Forward437 = function(inString)
    {
        
var encArray = new Array();
        
var tmp='';
        
var i=0;
        
var c=0;
        
var l=inString.length;
        
var cc;
        
var h;
        
for(;i<l;++i)
        {
            c
++;
            
if(c==128)
            {
                encArray.push(tmp);
                tmp
='';
                c
=0;
            }
            cc
=inString.charCodeAt(i);
            
if(cc<128)
            {
                tmp
+=String.fromCharCode(cc);
            }      
            
else
            {
                h
=this.d2h(cc);
                h
=forward[''+h];
                tmp
+=String.fromCharCode(this.h2d(h));
            }
        }
        
if(tmp!='')
        {
            encArray.push(tmp);
        }
        
// this loop progressive concatonates the

        
// array elements entil there is only one

        
var ar2=new Array();
        
for(;encArray.length>1😉
        {
            
var l=encArray.length;
            
for(var c=0;c<l;c+=2)
            {
                
if(c+1==l)
                {
                    ar2.push(encArray);
                }
                
else
                {
                    ar2.push(
''+encArray+encArray);
                }
            }
            encArray
=ar2;
            ar2
=new Array();
        }
        
return encArray[0];
    }
    
/* Convert a code page 437 char code to a octet number*/
    
this.Backward437 = function(inString)
    {
        
var encArray = new Array();
        
var tmp='';
        
var i=0;
        
var c=0;
        
var l=inString.length;
        
var cc;
        
var h;
        
for(;i<l;++i)
        {
            c
++;
            
if(c==128)
            {
                encArray.push(tmp);
                tmp
='';
                c
=0;
            }
            cc
=inString.charCodeAt(i);
            
if(cc<128)
            {
                tmp
+=String.fromCharCode(cc);
            }
            
else
            {
                h
=this.d2h(cc);
                h
=backward[''+h];
                tmp
+=String.fromCharCode(this.h2d(h));
            }
        }
        
if(tmp!='')
        {
            encArray.push(tmp);
        }
        
// this loop progressive concatonates the

        
// array elements entil there is only one

        
var ar2=new Array();
        
for(;encArray.length>1😉
        {
            
var l=encArray.length;
            
for(var c=0;c<l;c+=2)
            {
                
if(c+1==l)
                {
                    ar2.push(encArray);
                }
                
else
                {
                    ar2.push(
''+encArray+encArray);
                }
            }
            encArray
=ar2;
            ar2
=new Array();
        }
        
return encArray[0];
    }
   
}
// Example Code

/*
var bf0=new BinaryFile();
var crFolder = 'C:/Temp/cr'
var bf1=new BinaryFile(crFolder+"/PCDV0026.JPG");
var bf2=new BinaryFile(crFolder+"/PCDV0026_.JPG");
bf2.WriteAll(bf1.ReadAll());
*/

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

alex turner
I started out as a Chemist, but just keep being pulled back into IT.
I am now a senior architect with The Project Network (www.project-network.com)

Occupation: Web Developer
Location:  United Kingdom