做一名微软软件开发人员就像在国际煎饼屋订早餐一样。每道菜都有一堆煎饼,你必须从各种各样的煎饼和糖浆口味中选择。对于web应用程序,解决方案堆栈是一组软件子系统或组件,用于交付功能完整的解决方案(无论是产品还是服务)。例如,要开发web应用程序,微软开发人员需要使用和理解微软的组件栈,包括不断出现的开源工具、设计模式和第三方产品。这么多煎饼啊。本文的目标是浏览一个示例客户维护web应用程序,它实现了Microsoft堆栈中的最新技术,包括Microsoft . net 4.5.1、Visual Studio Express 2013、MVC 5、WebAPI 2和Entity Framework 6的最新beta版本。在本文中,我们将实现各种设计模式和技术,以帮助实现松耦合设计,从而促进通过n层web应用程序的各个层实现关注点分离。总的来说,这个示例应用程序的实现是我以前的代码项目文章《使用jQuery、JSON、Knockout和c#的MVC技术》的变体。实体框架附带了一个建模工具,可以让你设计你的领域和数据库对象——包括使用以下三个选项中的任何一个:数据库优先、模型优先或代码优先技术。通过为域对象创建POCO类,我选择使用“代码优先”的方法来创建域对象。创建POCO(普通的旧CLR对象)意味着你可以创建标准的。net类(用任何。net支持的语言编写)来定义你的域设计——不受特定框架所要求的属性或继承的限制。 隐藏,复制Code

// Customer Domain Entity
public class Customer
{
public Guid CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string PhoneNumber { get; set; }
public string CreditCardNumber { get; set; }
public Guid PaymentTypeID { get; set; }
public DateTime? CreditCardExpirationDate { get; set; }
public string CreditCardSecurityCode { get; set; }
public Decimal CreditLimit { get; set; }
public DateTime? DateApproved { get; set; }
public int ApprovalStatus { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateUpdated { get; set; }
}

数据层接口 此应用程序的设计目标之一是在应用程序的不同层之间创建松散耦合的交互。这个客户维护应用程序将有一个业务层,业务层依赖于数据层来与数据库交互。下面的接口将由数据层实现。为了使数据层与业务层松散耦合,业务层将不实例化数据层。数据层将被注入到业务层的构造函数中,并且只知道数据层的ICustomerDataService接口,而不知道数据访问层的具体实现。随着这个应用程序的增长,我们可能会决定用一种更传统的方法来替换实体框架数据访问层,这种方法包含ADO。NET和/或存储过程。此外,我们可以使用模拟数据层来执行持续集成单元测试。隐藏,MVC 5自带的默认项目模板包括Twitter引导。Bootstrap是一个用于创建网站和web应用程序的免费工具集合。它包含排版、表单、按钮、导航和其他界面组件的HTML和基于css的设计模板,以及可选的JavaScript扩展。这个客户维护项目的视图被格式化为引导样式表。在过去的几年里,由于我一直在使用MVC视图和Razor视图引擎,所以当涉及到视图时,我的设计偏好发生了一点变化。我的方法已经改变,使观点尽可能的干净。客户维护视图将主要包含普通的旧HTML,不包含任何服务器端脚本代码。视图中也将很少或没有对JavaScript的引用。视图和JavaScript之间的唯一链接将通过KnockoutJS数据绑定标记。这种方法使视图成为一段易于阅读的自包含HTML代码。当然,随着观点变得越来越复杂;最终将需要一些剃刀代码。幸运的是,Razor视图引擎是一种精确的、有用的、轻量级的语言,它使您能够在ASP中为MVC项目创建视图。NET同时仍然保持关注点分离,具有测试能力,并遵循基于模式的开发。收缩,复制Code

<!--- Customer Maintenance View --->
<divclass="hero-unit"data-bind="visible: DisplayContent"style="display:no">
<tableclass="table-condensed">
<tr>
<tdclass="input-label"><label>First Name:</label></td>
<tdclass="input-box">
<divdata-bind="visible: EditMode"class="edit-div">
<inputid="FirstName"type="text"data-bind="value: FirstName"/>
<spanclass="required-indicator">*</span>
</div>
<divclass="display-div"data-bind="text: FirstName, visible: DisplayMode "></div>
</td>
<tdclass="input-label"><label>Last Name:</label></td>
<tdclass="input-box">
<divdata-bind="visible: EditMode"class="edit-div">
<inputid="LastName"type="text"data-bind="value: LastName"/>
<spanclass="required-indicator">*</span>
</div>
<divclass="display-div"data-bind="text: LastName, visible: DisplayMode "></div>
</td>
</tr>
<tr>
<tdclass="input-label"><label>Email Address:</label></td>
<tdclass="input-box">
<divdata-bind="visible: EditMode"class="edit-div">
<inputid="EmailAddress"type="text"data-bind="value: EmailAddress"/>
<spanclass="required-indicator">*</span>
</div>
<divclass="display-div"data-bind="text: EmailAddress, visible: DisplayMode "></div>
</td>
<tdclass="input-label"><label>Phone Number:</label></td>
<tdclass="input-box">
<divdata-bind="visible: EditMode"class="edit-div">
<inputid="PhoneNumber"type="text"data-bind="value: PhoneNumber "/>
</div>
<divclass="display-div"data-bind="text: PhoneNumber, visible: DisplayMode "></div>
</td>
</tr>
</table>
</div> <divstyle="clear:both"></div>
<spandata-bind="visible: EnableCreateButton">
<inputid="btnCreate"type="button"value="Create"data-bind="click: CreateCustomer"class="btn btn-primary btn-medium"/>
</span>
</div>  

MVVM -模型视图视图模型KnockoutJS &JavaScript 客户维护视图的JavaScript将通过KnockoutJS与视图交互。Knockout是模型-视图-视图模型(Model- view - view Model, MVVM)模式的独立JavaScript实现。敲除有助于在表示HTML标记和要显示的数据之间提供清晰的分离。Knockout帮助您使用简洁、可读的语法轻松地将DOM元素与Knockout的视图模型数据关联起来。当数据模型的状态发生变化时,UI u自动pdates。客户维护项目中的JavaScript不会直接引用视图的文档对象模型(DOM)。使用HTML中的数据绑定标签,视图将被数据绑定到用JavaScript创建的视图模型。对视图数据和视图元素的访问将通过直接访问敲除视图模型进行,MVVM设计模式使视图和视图支持的JavaScript更加清晰,便于读写和测试。隐藏,收缩,Code

//KnockoutJS View Model Setup and Initialization
$(document).ready(function () {
InitializeCustomerMaintenanceViewModel();
GetCustomerInformation(); }); function CustomerMaintenanceViewModel() {
this.CustomerID = ko.observable("");
this.FirstName = ko.observable("");
this.LastName = ko.observable("");
this.EmailAddress = ko.observable("");
this.Address = ko.observable("");
this.City = ko.observable("");
this.Region = ko.observable("");
this.PostalCode = ko.observable("");
this.Country = ko.observable("");
this.PhoneNumber = ko.observable("");
this.CreditCardNumber = ko.observable("");
this.PaymentTypes = ko.observableArray();
this.PaymentTypeID = ko.observable("");
this.PaymentTypeDescription = ko.observable("");
this.CreditCardExpirationDate = ko.observable("");
this.CreditCardSecurityCode = ko.observable("");
this.CreditLimit = ko.observable("");
this.MessageBox = ko.observable("");
} function InitializeCustomerMaintenanceViewModel() {
customerMaintenanceViewModel = new CustomerMaintenanceViewModel();
ko.applyBindings(customerMaintenanceViewModel);
}

ASP副本。如果您使用的是asp.net Web forms,那么您可以使用asp.net。通过在ASPX页面中只包含HTML控件(不包括ASP),可以实现上述相同的客户端技术。NET服务器从页中控制并关闭视图状态。从本质上说,这将使您远离ASP。NET Web表单回发模型。除了Web表单的Page_Init或Page_Load事件中的任何初始用户身份验证和安全需求外,代码隐藏文件将为空。所有服务器端代码交互都将从web服务或RESTful服务启动,或者如果愿意,也可以从ASP启动。可以在您的asp.net页面方法中使用。净Web表单。最后,您将使用Web表单模拟MVC行为。因为这是一个MVC项目,视图将通过MVC控制器呈现。为了保持简洁,这个项目的控制器将承担最小的责任,即简单地验证用户并将视图呈现给客户端。在编辑客户的情况下,customerID将被传递到控制器方法中,然后customerID将通过视图模型类对象传递给视图。隐藏,复制Code

public class CustomersController : Controller
{
/// Customer Maintenance
[AuthenicationAction]
public ActionResult CustomerMaintenance()
{
return View("CustomerMaintenance");
} /// Customer Inquiry
[AuthenicationAction]
public ActionResult CustomerInquiry()
{
return View("CustomerInquiry");
} /// Edit Customer
[AuthenicationAction]
public ActionResult EditCustomer(string customerID)
{
CustomerMaintenanceViewModel viewModel = new CustomerMaintenanceViewModel();
viewModel.Customer.CustomerID = new Guid(customerID);
return View("CustomerMaintenance", viewModel);
}
}

jQuery AJAX 当客户维护视图加载时,它将发出一个jQuery AJAX调用来检索视图中显示所需的所有数据。这种方法提供了更好的用户体验,允许部分呈现视图,并允许页面看起来更具有响应性,而不是让用户等待页面的全部内容一次全部呈现。隐藏,收缩,复制Code

function GetCustomerInformation()
{
MVC5WebApplication.DisplayAjax(); var customer = new function () { };
customer.CustomerID = customerMaintenanceViewModel.CustomerID();
var jqxhr = $.get(
"/api/customers/GetCustomerMaintenanceInformation",
customer,
function (response) {
GetCustomerCompleted(response);
},
"json")
.fail(function (response) {
RequestFailed(response);
}
);
} function GetCustomerCompleted(response) { customerMaintenanceViewModel.PaymentTypes(response.PaymentTypes);
customerMaintenanceViewModel.FirstName(response.Customer.FirstName);
customerMaintenanceViewModel.LastName(response.Customer.LastName);
customerMaintenanceViewModel.Address(response.Customer.Address);
customerMaintenanceViewModel.City(response.Customer.City);
customerMaintenanceViewModel.Region(response.Customer.Region);
customerMaintenanceViewModel.PostalCode(response.Customer.PostalCode);
customerMaintenanceViewModel.Country(response.Customer.Country);
customerMaintenanceViewModel.PhoneNumber(response.Customer.PhoneNumber);
customerMaintenanceViewModel.EmailAddress(response.Customer.EmailAddress); MVC5WebApplication.HideAjax();
}

jQuery AJAX HTTP POST 创建一个新客户,用户将按下create customer按钮,并执行以下JavaScript。下面的JavaScript将阻塞UI(使用来自malsup.com的jQuery插件),显示服务器请求正在运行的AJAX消息,并用来自视图模型的数据填充customer对象。使用jQuery AJAX POST方法,客户对象将自动序列化为一个JSON对象,并通过HTTP线发送到服务器,服务器路由名为“/api/customers/create”。隐藏,收缩,客户维护项目的AJAX请求将通过微软最新版本的ASP与服务器交互。NET WebAPI 2.0组件。ASP。NET WebAPI是一个框架,它可以很容易地构建HTTP服务,这些服务可以覆盖广泛的客户端,包括浏览器和移动设备。ASP。NET WebAPI是在。NET框架上构建RESTful应用程序的理想平台。这个框架充满了抽象。控制器、筛选器提供程序、模型验证器和许多其他组件构成了框架的管道。通过创建模仿MVC控制器并从MVC路由系统借用的WebAPI控制器来创建Web api。WebAPI的第一个版本使用了基于约定的路由。当框架收到请求时,它将URI与HTTP谓词(GET、POST、PUT、DELETE等)驱动的路由进行匹配。要创建客户,将使用HTTP谓词POST,并使用PUT谓词使用用于检索客户的GET谓词更新客户。在发布简单的公共RESTful API时,这已经足够了。对于单一实体(发票、销售订单、采购订单等)需要多种类型更新(包括更新、批准和撤销实体)的业务应用程序,简单的基于约定的路由是不合适的。 WebAPI的版本2引入了一种新的路由类型,称为属性路由。顾名思义,属性路由使用属性来定义路由。属性路由使您可以对web API中的uri进行更多的控制。例如,您可以轻松地使用相同的POST谓词,该谓词路由到执行不同类型函数的不同WebAPI方法调用 要在MVC web应用程序中启用属性路由,需要修改App_Start文件夹中的WebApiConfig类,并将config.MapHttpAttributeRoutes()行添加到Register方法中。隐藏,复制Code

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}  

WebAPI Controllers 创建WebAPI的,您添加(脚手架)一个MVC WebAPI控制器到您的MVC Web项目的Controllers文件夹。如果您使用过ASP。NET MVC,那么你已经很熟悉控制器了。它们在W中的作用是相似的但WebAPI中的控制器派生自ApiController类而不是控制器类。您将注意到的主要区别是,WebAPI控制器上的动作不返回视图,它们返回数据。隐藏,收缩,属性定义了一个HTTP GET方法。字符串“GetCustomerInformation”是“api/customers/GetCustomerInformation”路由的URI模板。属性定义了一个HTTP POST方法,其路由为“create”,该路由映射到“api/customers/create”。您可以使用[RoutePrefix("api/customers")]这样的属性在类级别上添加路由前缀,而不是在每个方法上指定整个路由路径。诸如HttpGet和HttpPost这样的路由属性还限制客户端应用程序通过指定的HTTP谓词调用这些路由。,才能隐藏,attribute 是一个定制的WebAPI动作过滤器,它将使用表单身份验证对用户进行身份验证,并在执行方法之前执行以下代码。这个动作过滤器类似于动作过滤器[AuthenicationAction],它在呈现视图时用于在MVC控制器方法中对用户进行身份验证。 [WebApiAuthenication]操作过滤器将覆盖WebAPI控制器操作的onactionexecution事件。自从WebAPI控制器方法只会从客户维护程序,过滤器将验证方法调用正在从一个AJAX请求执行检查的请求头属性叫X-Requested-With确定请求来自一个AJAX请求。此外,这个自定义筛选器将检查用户是否已经过身份验证。如果这些条件中有一个没有被满足,那么过滤器将返回一个坏的请求响应HTTP 400状态代码回给调用的客户端,并且WebAPI操作将不会被执行。隐藏,收缩,当WebAPI接收到HTTP请求时,它会根据动作的签名将请求转换成。net类型。动态模型绑定用于自动填充操作的参数。在Create Customer操作中,我创建了一个客户数据转换对象,该对象将通过WebAPI模型绑定实例化和填充。由于Create Customer请求是通过POST请求发送的,所以请求的值将驻留在请求体中。要从请求体中读取值,需要一个[FromBody]属性来告诉模型绑定器如何填充动作签名。由于WebAPI只允许用[FromBody]属性标记一个参数,因此需要一个客户数据转换对象。客户数据转换对象客户数据转换对象(DTO)类似于客户域实体对象。这里可以使用customer域实体对象,但是如果您打算构建一个将来会增长的应用程序,那么应该考虑使用DTO对象,因为域实体对于数据转换不是最优的。域实体在客户端总是有您需要的或多或少的属性,这可能导致过度绑定,并使模型绑定效率低下。隐藏,复制code

public class CustomerMaintenanceDTO
{ public Guid CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string PhoneNumber { get; set; }
public string CreditCardNumber { get; set; }
public Guid? PaymentTypeID { get; set; }
public DateTime? CreditCardExpirationDate { get; set; }
public string CreditCardSecurityCode { get; set; }
public Decimal? CreditLimit { get; set; } }

模型绑定验证当客户端向您的web API发送数据时,模型绑定将在进行任何处理之前验证数据。任何不能转换为。net类型的参数都会将模型的状态设置为无效。此外,还可以在DTO对象上添加注释以进行数据验证(例如标记所需的某些参数等)。当验证失败时,WebAPI不会自动向客户端返回错误。由WebAPI控制器动作来检查模型状态并作出适当的响应。在下面的代码片段中,模型状态的状态将被检查,如果模型状态无效,控制器操作将返回一个HTTP status 400 Bad请求代码给客户端。隐藏,复制Code

if (!ModelState.IsValid)
{ var errors = ModelState.Errors(); viewModel.ReturnMessage = ModelStateHelper.ReturnErrorMessages(errors);
viewModel.ReturnStatus = false; var badResponse = Request.CreateResponse<CustomerMaintenanceViewModel>
(HttpStatusCode.BadRequest, viewModel); return badResponse;
}  

HTTP状态码当Create Customer操作成功执行时,它将返回一个HTTP 201状态码,指定所请求的数据已经创建。RESTful服务通常构建在HTTP之上。协议通过HTTP状态码的方式传递请求的状态。在应用程序中实现所有状态代码是很困难的,但是作为一种需求和最佳实践,您的所有WebAPI操作都应该返回一个HTTP状态代码。也许最好的方法是限制暴露给少数人的状态码的数量。您的客户端可以基于这些HTTP代码采取操作并相当容易地实现它们。HTTP请求消息最后,WebAPI还提供了查看发送到动作的整个HTTP请求消息的功能。您可以通过提供HttpRequestMe来访问该信息您的动作签名中的ssage参数。这个客户维护应用程序的目标之一是创建松散耦合的层。WebAPI方法使用的客户业务层依赖于数据访问层。数据访问层将通过其构造函数传递到业务层,而不是在业务层中直接实例化数据访问层。数据访问层将使用Ninject注入到WebAPI控制器的构造函数中。这将允许我们交换数据访问层,而不必对业务层进行任何更改。出于单元测试的目的,我们可能希望使用数据访问层的模拟版本,它不会真正触及数据库。Ninject是一个快速、轻量级的。net应用依赖注入器。它帮助您将应用程序分解为松耦合的、高内聚的部分集合。通过使用Ninject来支持你的软件架构,你的代码将变得更容易编写、重用、测试和修改。在创建一个新的MVC应用程序后,运行安装包Ninject。从Visual Studio中的包管理器控制台。这将下载并安装所有必需的程序集,并添加一些源代码以将其集成到应用程序中。安装完成后,您将在App_Start文件夹中找到一个NinjectWebCommon.cs文件。这个类文件将包含配置Ninject所需的代码。 NuGet包Manager  NInject。MVC3也可以从Nuget软件包库中找到并安装。NuGet是一个Visual Studio扩展,它使得在使用. net框架的Visual Studio项目中轻松添加、删除和更新库和工具。当您添加库或工具时,NuGet会将文件复制到您的解决方案中,并自动对项目中需要的任何更改进行更改,例如添加引用和更改app.config或web。配置文件。当您删除一个库时,NuGet将删除文件并逆转它在您的项目中所做的任何更改,以便不留下任何混乱。 下面的NinjectWebCommon.cs类文件,注册你的依赖对象。隐藏,收缩,复制code

public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper(); /// Starts the application
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule)); bootstrapper.Initialize(CreateKernel);
}
/// Stops the application.
public static void Stop()
{
bootstrapper.ShutDown();
} /// Creates the kernel that will manage your application.
private static IKernel CreateKernel()
{
var kernel = new StandardKernel(); kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); RegisterServices(kernel); // Install our Ninject-based IDependencyResolver into the Web API config
GlobalConfiguration.Configuration.DependencyResolver =
new NinjectDependencyResolver(kernel); return kernel;
} /// Load your modules or register your services here!
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<MVC5DataServiceInterface.ICustomerDataService>().To
<MVC5EntityFrameworkDataAccess.EFCustomerService>();
}
}

registration对象到Ninject 在RegisterServices方法中,使用Ninject内核。Bind命令来注册依赖对象。通过注册依赖对象,Ninject将实例化并根据构造函数的签名将你注册的对象注入到你的类构造函数中。在这种情况下,根据类接口实现类可以将不同的对象注入实现相同接口的构造函数中。在上面的示例中,我注册了Ninject,它是实现ICustomerDataService接口的客户数据服务组件的实体框架版本。 依赖注入(DI)实现了控制反转(IoC)编程技术。控制反转和依赖注入模式都是关于从代码中移除依赖关系的。 在下面的WebAPI类构造函数中,对象耦合是在运行时绑定的,而实际的对象在编译时是未知的。在NinjectWebCommon类中,只需一行代码,就可以告诉Ninject内核在运行时加载哪个对象。例如,您可以提供使用实体框架实现的数据访问层,也可以切换到ADO。基于网络的数据访问层。为了进行单元测试,还可以注入数据访问层的模拟对象。示例客户维护应用程序包含实现所有这三个不同数据访问层的代码。隐藏,WebAPI的难点在于Ninject绑定不能应用于ApiController实例。当您尝试命中一个API端点时,您将收到一个错误,说明您的控制器没有默认构造函数。要解决此问题,需要设置依赖项解析器。 要处理依赖项解析,您需要创建两个将由NinjectWebCommon类使用的自定义类。下面的依赖解析器代码被添加到NinjectWebCommon类文件中,可以在各种技术博客中找到。查看Phil Haack关于依赖解析器的博客。隐藏,收缩,复制code

public class NinjectDependencyScope : IDependencyScope
{
IResolutionRoot resolver;
public NinjectDependencyScope(IResolutionRoot resolver)
{
this.resolver = resolver;
}
public object GetService(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.GetAll(serviceType);
}
public void Dispose()
{
IDisposable disposable = resolver as IDisposable;
if (disposable != null)
disposable.Dispose();
resolver = null;
}
}
public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
IKernel kernel;
public NinjectDependencyResolver(IKernel kernel)
: base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}

业务层客户维护项目的业务层包含用于创建、更新、验证和检索客户信息的关键方法。将ICustomerDataService接口传入业务层,可以很容易地保持业务逻辑层独立于数据访问层。数据访问层的底层具体方法对业务层是隐藏的。 从和传递客户对象和通用的客户对象列表到数据访问层,而不管什么数据访问技术正在冲击数据库。这使得在不更改业务层的情况下切换数据访问层变得很容易。正如您在下面看到的,在业务层中没有显式的数据访问代码。例如,CreateSession方法可以初始化实体框架数据上下文对象,也可以使用标准的ADO创建数据库连接。取决于哪个数据访问层被注入到业务层。隐藏,收缩,在为本文开发示例应用程序时,我决定通过NuGet包管理工具安装NUnit单元测试工具,这样我就可以针对应用程序的业务层编写一些示例单元测试。在编写了几个单元测试之后,我意识到单元测试似乎在理论上比在实践中更流行。你为什么会这样认为?嗯,这是一个巨大的障碍,因为建立现实和有用的测试是困难的。 在编写单元测试时,我还意识到我实际上是在编写集成测试,而根本不是单元测试。关于编写好的单元测试,我学到的几个关键知识是:编写好的单元测试包括先编写测试,然后使用测试驱动开发(TDD)方法编写代码。这可以确保您正在编写可测试的代码。您还需要开发和编写类来实现接口。这将允许您注入外部依赖关系的模拟对象,比如数据库和其他数据存储和文件系统。此外,您应该创建一个流程,以便在每次运行单元测试套件之前刷新测试数据。如果您希望实现一个持续集成过程,在此过程中,在每次代码签入和软件构建运行之后,您的所有单元测试套件都将运行,那么这将非常有用。 这个示例应用程序包含可以刷新SQL-Server Express中的数据库表的测试数据。您还可以使用此测试数据在模拟数据访问层中构建不实际触及数据库的模拟数据。为了刷新数据,我包含了一个种子类库,它可以从应用程序菜单或模拟的数据访问对象中运行。 当下面的NUnit测试启动时,它执行InitializeDependencies方法,因为它被标记为[SetUp]属性。这是标记一个类的属性,该类包含给定名称空间下所有测试fixture的一次性设置。该类最多可以包含一个用[SetUp]属性标记的方法和一个用[TearDown]属性标记的方法。 下面的InitializeDependencies方法将有条件地创建要用于测试目的的特定ICustomerDataService对象。在持续集成环境中,您将希望为某些需要外部资源(如数据库、数据存储等)的对象创建模拟对象。您希望这样做是因为您可能有数百个测试需要执行,并且您希望在开发人员签入启动自动构建和测试的代码时及时运行测试。此外,您可能还会使用不同的配置来执行依赖项,并在夜间运行应用程序的构建。 在下面的例子中,我有三个测试方法。NUnit将执行具有[Test]属性的方法。您可以在测试方法中使用NUnit断言方法来评估测试的条件。最佳实践是将每个单元测试限制为每个单元测试只有一个断言。隐藏,收缩,NUnit测试运行程序(那是与NUnit NuGet包一起下载的)是一个图形化运行程序。它在类似于资源管理器的浏览器窗口中显示测试,并提供测试成功或失败的可视化指示。它允许您有选择地运行单个测试或套件,并在修改和重新编译代码时自动重新加载。您还可以通过命令行运行测试,该命令行可以包含在持续集成构建和测试环境中。最后,没有实现您喜爱的数据网格控件,任何web应用程序都是不完整的。按照这个示例应用程序的设计原则,我希望所有UI表示功能都驻留在客户机上并在客户机上执行。这意味着用JavaScript将数据网格数据绑定函数移动到客户端。传统上数据绑定是在服务器端执行的,但我只想让服务器端简单地将JSON集合序列化到客户机。所有这些促使我寻找一个好的客户端数据网格。在我的研究中,我从第三方供应商那里找到了大约20种基于jQuery的数据网格和一些数据网格控件。这一切都有点令人畏惧和难以承受因此,我决定简单地编写一个普通的旧HMTL表,并用KnockoutJS用几行JavaScript代码将一个JSON集合从服务器绑定到表中。下面是客户查询网格的HTML。它基本上是一个HTML表,头行用thead标记和tbody标记定义,用于显示客户查询网格的数据行。将KnockoutJS数据绑定标签添加到HTML表中,将允许KnockoutJS自动将我们希望显示的每个客户的数据绑定到网格中。如您所见,您只需要定义一行HTML,其中包含一个foreach属性的数据绑定标记。使用foreach属性,KnockoutJS将自动为每个客户行生成HTML。隐藏,复制Code

<tableid="CustomerInquiryTable"border="0"class="table"style="width: 100%;">
<thead>
<tr>
<thstyle="width: 15%; height: 25px">Last Name</th>
<thstyle="width: 15%">First Name</th>
<thstyle="width: 35%">Email Address</th>
<thstyle="width: 15%">City</th>
<thstyle="width: 20%">Payment Type</th>
</tr>
</thead>
<tbodydata-bind="foreach: Customers">
<tr>
<td><divdata-bind="text: LastName "></div></td>
<td><divdata-bind="text: FirstName "></div></td>
<td><divdata-bind="text: EmailAddress"></div></td>
<td><divdata-bind="text: City"></div></td>
<td><divdata-bind="text: PaymentTypeDescription"></div></td>
</tr>
</tbody>
</table>

KnockoutJS视图模型要用KnockoutJS与客户查询数据网格交互,需要一些JavaScript来创建和初始化KnockoutJS视图模型。为了将客户数据绑定到HTML表,在视图模型中定义了客户的KnockoutJS可观察数组。如果您想要检测并响应一个对象上的更改,您可以使用KnockoutJS observable。如果您希望检测并响应对象集合的更改,请使用observableArray。这在许多场景中非常有用,比如在数据网格中,您需要显示或编辑多个值,并且需要UI的重复部分在添加和删除项时出现或消失。 隐藏,复制code

$(document).ready(function () {
InitializeCustomerInquiryViewModel();
}); function InitializeCustomerInquiryViewModel() { customerInquiryViewModel = new CustomerInquiryViewModel();
ko.applyBindings(customerInquiryViewModel); } function CustomerInquiryViewModel() { this.Customers = ko.observableArray("");
this.TotalCustomers = ko.observable();
this.TotalPages = ko.observable();
this.PageSize = ko.observable(pageSize); }  

客户端数据绑定与KnockoutJS, AJAX和json&传统ASP。NET数据绑定总是发生在服务器端后面的代码中,特别是asp.net。净Web表单。为了实现此项目的关注点设计目标分离,客户查询数据网格的数据绑定将在客户机上以JavaScript进行。第一步是通过访问WebAPI路由对服务器进行AJAX调用,该路由将返回一个包含客户数据JSON集合的HTTP响应。服务器将自动将。net通用客户数据列表序列化为一个JSON集合,该JSON集合将通过HTTP传输。 将客户数据绑定到客户查询HTML表就像用JavaScript循环JSON集合并在KnockoutJS视图模型中将行填充(“推入”)到客户可观察数组一样简单。for循环完成后,KnockoutJS将完成其余的工作,通过HTML中的数据绑定标记自动呈现显示客户行所需的HTML。最后,我们所有的数据绑定都在客户端执行。我相信用JavaScript编写的客户端数据绑定可以促进开发更简洁的代码。遵循关注点分离原则,数据绑定是一个表示层函数,应该在客户机上执行。隐藏,为了使客户查询数据网格更健壮,我添加了过滤、分页和排序网格的功能。客户端将调用GetCustomers WebAPI路由并传递过滤、排序和分页信息。然后,WebAPI控制器动作将调用业务层,同时还将数据访问层注入业务层的构造函数。 数据访问层分页在客户数据访问层实现了数据的过滤、分页和排序。下面是使用LINQ根据实体框架上下文对象查询客户数据的代码。 实体框架分页示例使用了动态LINQ。为了实现这个功能,我必须从微软下载动态LINQ库的源代码,并将其保存在一个名为DynamicLibrary.cs的c#类文件中,然后引用命名空间System.Linq.Dynamic。动态LINQ库实现了IQueryable接口来执行它的操作。这是必需的,因为我需要能够将文字字符串值传递到LINQ的Lambda表达式语法中。 下面的LINQ查询将客户表与支付类型表连接起来,因为我们希望显示每个客户的信用卡支付类型。数据层返回一个对象的通用列表,因此需要一个CustomerInquiry类,它包含customer表和Payment类型表的属性。使用LINQ Skip方法允许查询基于我们希望为网格返回的页大小和当前页号返回单个页的客户数据。,,,,,,,,, 隐藏,收缩,复制Code

/// Customer Inquiry     
public List<CustomerInquiry> CustomerInquiry(
string firstName, string lastName, DataGridPagingInformation paging)        
{
    string sortExpression = paging.SortExpression;     if (paging.SortDirection != string.Empty)
        sortExpression = sortExpression + " " + paging.SortDirection;     var customerQuery = dbConnection.Customers.AsQueryable();     if (firstName != null && firstName.Trim().Length > 0)
    {
        customerQuery = customerQuery.Where(c => c.FirstName.StartsWith(firstName));
    }     if (lastName != null && lastName.Trim().Length > 0)
    {
        customerQuery = customerQuery.Where(c => c.LastName.StartsWith(lastName));
    }     var customers = 
    from c in customerQuery
    join p in dbConnection.PaymentTypes on c.PaymentTypeID equals p.PaymentTypeID
select new { c.CustomerID,c.FirstName,c.LastName,c.EmailAddress,c.City,p.Description };     int numberOfRows = customers.Count();     customers = customers.OrderBy(sortExpression);     var customerList = customers.Skip((paging.CurrentPageNumber - 1) 
                     * paging.PageSize).Take(paging.PageSize);     paging.TotalRows = numberOfRows;
    paging.TotalPages = Utilities.CalculateTotalPages(numberOfRows, paging.PageSize);     List<CustomerInquiry> customerInquiry = new List<CustomerInquiry>();     foreach (var customer in customerList)
    {
        CustomerInquiry customerData = new CustomerInquiry();
        customerData.CustomerID = customer.CustomerID;
        customerData.FirstName = customer.FirstName;
        customerData.LastName = customer.LastName;
        customerData.EmailAddress = customer.EmailAddress;
        customerData.City = customer.City;
        customerData.PaymentTypeDescription = customer.Description;                      
        customerInquiry.Add(customerData);
    }     return customerInquiry;
            
}     

分页和ADO。对于那些喜欢执行传统SQL-Server T-SQL语句的用户,示例客户维护应用程序包含一个基于ADO的数据访问层对象。净和t - sql。下面的示例代码执行与LINQ/Entity框架版本相同的分页功能。因为这两个数据访问层都实现了ICustomerDataService接口,所以可以进行交换只需简单地更改Ninject配置文件,而不需要对示例客户维护应用程序进行任何额外更改,即可实现两者之间的过渡。 隐藏,收缩,滚动您自己的数据网格或使用第三方控件这都很有趣,我喜欢完全控制数据网格。如果您更喜欢使用第三方供应商(如Infragistics或Telerik)提供的数据网格,或者更喜欢使用开源的jQuery网格,请确保数据网格控件不绑定或依赖于任何服务器端引用。这将帮助您维护关注点分离(SoC)设计模式。结论:自从第一个软件系统被实施以来,人们就明白模块化是很重要的。软件工程中最重要的原则之一是关注点分离(SoC):即必须将软件系统分解为在功能上尽可能少重叠的部分。本文演示了在web应用程序开发中实现这一原则的各种技术。最终,您将得到一个允许不同的人独立地对系统的各个部分进行工作的体系结构,这个体系结构促进了系统的可重用性和可维护性,并允许轻松添加新特性。也许更重要的是,它使每个人都能更好地理解这个系统。本文所包含的技术—Visual Studio 2013 Express Preview for Web Microsoft SQL-Server Express 2012 Microsoft .NET Framework 4.5.1 微软实体框架6.0测试版 微软的ASP。净MVC 5 Twitter引导 微软WebAPI 2 KnockoutJS Ninject NUnit 块UI ToastrJS 本文转载于:http://www.diyabc.com/frontweb/news19512.html

使用MVC 5、Web API 2、KnockoutJS、Ninject和NUnit开发、架构和测试Web应用程序的更多相关文章

  1. 【ASP.NET Web API教程】2.3 与实体框架一起使用Web API

    原文:[ASP.NET Web API教程]2.3 与实体框架一起使用Web API 2.3 Using Web API with Entity Framework 2.3 与实体框架一起使用Web ...

  2. 【ASP.NET Web API教程】2.1 创建支持CRUD操作的Web API

    原文 [ASP.NET Web API教程]2.1 创建支持CRUD操作的Web API 2.1 Creating a Web API that Supports CRUD Operations2.1 ...

  3. ASP.NET Web API与Owin OAuth:调用与用户相关的Web API

    在前一篇博文中,我们通过以 OAuth 的 Client Credential Grant 授权方式(只验证调用客户端,不验证登录用户)拿到的 Access Token ,成功调用了与用户无关的 We ...

  4. Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2

    https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/individual-accounts-in-web-api Ind ...

  5. ASP.NET Web API与Owin OAuth:调用与用户相关的Web API(非第三方登录)

    授权完成添加属性 ClaimsIdentity oAuthIdentity = await CreateAsync(user/*userManager*/, OAuthDefaults.Authent ...

  6. MVC项目实践,在三层架构下实现SportsStore-09,ASP.NET MVC调用ASP.NET Web API的查询服务

    ASP.NET Web API和WCF都体现了REST软件架构风格.在REST中,把一切数据视为资源,所以也是一种面向资源的架构风格.所有的资源都可以通过URI来唯一标识,通过对资源的HTTP操作(G ...

  7. ASP.NET Web API和ASP.NET Web MVC中使用Ninject

    ASP.NET Web API和ASP.NET Web MVC中使用Ninject 先附上源码下载地址 一.准备工作 1.新建一个名为MvcDemo的空解决方案 2.新建一个名为MvcDemo.Web ...

  8. 在ASP.NET Web API和ASP.NET Web MVC中使用Ninject

    先附上源码下载地址 一.准备工作 1.新建一个名为MvcDemo的空解决方案 2.新建一个名为MvcDemo.WebUI的空MVC应用程序 3.使用NuGet安装Ninject库   二.在ASP.N ...

  9. [ASP.NET MVC 小牛之路]18 - Web API

    Web API 是ASP.NET平台新加的一个特性,它可以简单快速地创建Web服务为HTTP客户端提供API.Web API 使用的基础库是和一般的MVC框架一样的,但Web API并不是MVC框架的 ...

随机推荐

  1. 牛客多校训练AFJ(签到)

    题目链接https://ac.nowcoder.com/acm/contest/881/A(单调栈) #include<cstdio> #include<iostream> # ...

  2. Netty学习笔记-入门版

    目录 Netty学习笔记 前言 什么是Netty IO基础 概念说明 IO简单介绍 用户空间与内核空间 进程(Process) 线程(thread) 程序和进程 进程切换 进程阻塞 文件描述符 文件句 ...

  3. Codeforces1365

    AC代码 A. Matrix Game 对于给定矩阵,剩余可用的位置的数目是确定的,根据奇偶性判断就完事了. B. Trouble Sort 如果数组\(b\)有0有1,那么Yes.否则只有数组\(a ...

  4. istio部署

    Istio的部署介绍 目录 Istio的部署介绍 部署模型 集群模式 单集群 多集群 网络模型 单网络 多网络 控制面模型 身份和信任模型 网格中的信任 网格之间的信任 网格模型 单网格 多网格 租户 ...

  5. 12 props 传的是数组处理

    <template> <div>InfoDetailed</div> </template> <script> export default ...

  6. docker 停止、启动、删除镜像指令

    容器 docker ps // 查看所有正在运行容器 docker stop containerId // containerId 是容器的ID docker ps -a // 查看所有容器 dock ...

  7. C#中TextBox设置readonly不能读取数据问题

    在ASP.NET中前端设置控件TextBox的属性为Readonly="True"时,如果之前有设定初始值,或通过JS方式给其赋值后,在后台访问其Text值却无法获取,这种问题的解 ...

  8. python之class Meta用法

    Django model中的 class Meta 详解   通过一个内嵌类 "class Meta" 给你的 model 定义元数据, 类似下面这样: class Foo(mod ...

  9. VS调试出现解决 尝试加载 Oracle 客户端库时引发 BadImageFormatException。如果在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现此问题

  10. Linux 系统中环境变量/etc/profile、/etc/bashrc、~/.bashrc的区别

      /etc/profile./etc/bashrc.~/.bashrc的区别   1> etc目录下存放系统管理和配置文件 (系统配置) etc/profile:  profile为所有的用户 ...