RESTful日#1:企业级应用程序架构,使用实体框架、通用存储库模式和工作单元的Web api

表的内容

  • 介绍
  • 路线图
  • 休息
  • 设置数据库
  • Web API项目
  • 设置数据访问层
  • 通用存储库和工作单元
  • 工作单元
  • 设置业务实体 产品实体令牌实体用户实体
  • 设立业务服务项目
  • 设置WebAPI项目
  • 运行应用程序
  • 设计缺陷
  • 结论

介绍

在过去的几天里,我一直在练习和阅读大量关于RESTful服务的内容。令我惊讶的是,我找不到一个完整的ASP的实际实现系列。NET Web API在Web上。在本系列中,我将重点讨论如何使用Web API开发基本的企业级应用程序体系结构。

整个系列将侧重于较少的理论和更多的实际场景,以理解如何使用ORM(对象关系映射)创建RESTful服务,这里我选择实体框架。 本系列的第一篇文章将使用Asp.net MVC建立基于REST服务的应用程序的基本体系结构。在本文中,我将解释如何公开服务的纯REST(具象状态传输)端点,任何希望使用REST服务的客户机都可以使用这些端点。我将结合存储库模式和工作单元来解释实体框架的实现。我还将重点介绍如何为应用程序扩展时可能出现的所有实体创建通用存储库,简而言之,它应该是可伸缩和可扩展的。

本系列的第二篇文章讨论松耦合体系结构。这篇文章说明了我们如何在Asp中实现松耦合的体系结构。Net WebAPI使用UnityContainer和Bootstrapper。我将尝试实现依赖注入来达到同样的效果。

我的第三篇文章是关于克服UnityContainer的缺陷。它解释了我们如何利用MEF(Managed Extensibility Framework)在运行时使用IOC(Inversion of Control)解决依赖关系。

与其他web服务不同,Asp.net WebAPI支持基于属性的路由技术。在本系列的第4天,我们将解释如何使用属性路由获得WebAPI的多个端点,并克服基于REST的服务的传统缺点。

安全性一直是企业级应用程序所关注的问题,本系列文章的第5天将解释如何实现基于令牌的自定义身份验证技术,以使API更加安全。

本文的第6部分将解释如何在操作过滤器和异常过滤器的帮助下实现集中的日志记录和异常处理方法。它介绍了如何结合过滤器实现nLog。

开发没有单元测试的企业级基础设施是不值得开发的。第7天教会我们如何利用nUnit来完成webapi中的单元测试任务。我将在文章的第7天使用实际的例子。

这个系列的第8天教授了OData的新概念。它教人们如何用自定义的需求和要求来请求服务,如何实现对webapi的OData支持。

,

,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, Image 1

让我们从建立学习的路线图开始,

路线图

Image 2

我对这个系列的看法如下,

  • RESTful日#1:企业级应用程序架构,使用实体框架、通用存储库模式和工作单元的Web api。
  • RESTful日#2:使用Unity容器和引导程序在Web api中使用依赖注入实现控制反转。
  • RESTful日#3:使用Unity容器和可管理扩展框架(MEF)在Asp.net Web api中使用控制反转和依赖注入来解决依赖关系的依赖关系。
  • RESTful日#4:使用MVC 4 Web api中的属性路由自定义URL重写/路由。
  • RESTful日#5:使用操作过滤器的Web api中基于基本身份验证和令牌的自定义授权。
  • RESTful日#6:使用操作过滤器、异常过滤器和NLog在Web api中进行请求日志记录和异常处理/日志记录。
  • RESTful日#7:使用NUnit和Moq框架在WebAPI中进行单元测试和集成测试(第一部分)。
  • RESTful日#8:使用NUnit和Moq框架在WebAPI中进行单元测试和集成测试(第二部分)
  • RESTful日#9:扩展ASP中的OData支持净Web api。
  • RESTful日#10:创建自托管的ASP。NET WebAPI与CRUD操作在Visual Studio 2010

我有意使用Visual Studio 2010和。net Framework 4.0,因为在。net Framework 4.0中很少有很难找到的实现,但我将通过演示如何实现来简化它。

休息

这是维基百科的一段摘录,

与基于soap的web服务不同,没有针对rest web api的“官方”标准。这是因为REST是一种架构风格,而SOAP是一种协议。尽管REST本身不是一个标准,但大多数REST实现都使用了HTTP、URI、JSON和XML等标准。

我同意。我们来写一些代码。

设置数据库

我使用SQL Server 2008作为数据库服务器。我已经提供了在sql Server中创建数据库的sql脚本,您可以使用相同的sql脚本创建一个数据库。我已经给WebApiDb作为我的数据库名。我的数据库目前包含三个表:产品、令牌和用户。在本教程中,我们将只处理使用Web API和实体框架来执行凝块操作的product table。在下一篇文章中,我们将使用令牌和User。对于那些无法通过脚本创建数据库的人,可以遵循以下结构,

Image 3

Web API项目

打开你的Visual Studio,我使用的是VS 2010,你可以使用VS 2010或以上版本。

步骤1:在visual studio中创建一个新项目,

Image 4

步骤2:在那里选择创建ASP。NET MVC 4 Web应用程序,给它一个你选择的名字,我给它WebAPI。

Image 5

步骤3:在显示的不同类型的项目模板中,选择Web API项目,

Image 6

完成后,您将得到一个如下所示的项目结构,带有一个默认的Home和Values控制器。

你可以选择删除这个ValuesController,因为我们将使用自己的控制器来学习。

Image 7

设置数据访问层

让我们先设置或数据访问层。我们将使用实体框架5.0与数据库对话。我们将使用通用存储库模式和工作单元模式来标准化我们的层。

让我们来看看微软给出的实体框架的标准定义:

“微软ADO。NET Entity Framework是一个对象/关系映射(ORM)框架,它使开发人员能够将关系数据作为特定于领域的对象来使用,从而消除了开发人员通常需要编写的大多数数据访问代码的需求。使用实体框架,开发人员使用LINQ发出查询,然后检索和操作强类型对象的数据。实体框架的ORM实现提供了诸如变更跟踪、身份解析、延迟加载和查询转换等服务,这样开发人员就可以专注于特定于应用程序的业务逻辑,而不是数据访问基础。

在简单的语言中,实体框架是一个对象/关系映射(ORM)框架。这是对ADO的增强。NET,一个上层ADO。NET为开发人员提供了一种自动的机制来访问和存储数据库中的数据。

步骤1:在visual studio中创建一个新的类库,并将其命名为DataModel,如下所示,

Image 8

Image 9

Step2:用同样的方法,再创建一个项目,也就是一个类库,并将其称为BusinessEntities,

Image 10

我将很快解释这个类库的使用。

步骤3:继续到您的DataModel项目,右键单击它并添加一个新项目,在显示的列表中选择ADO。NET数据模型,并将其命名为WebApiDataModel.edmx。

Image 11

文件.edmx将包含我们之前创建的数据库的数据库信息,让我们设置一下。你会看到一个如下的向导,

Image 12

选择,从数据库生成。选择如下图所示的Microsoft SQl Server,

Image 13

单击continue,然后提供数据库的凭据,即WebAPIdb,并连接它,

Image 14

你会看到一个屏幕,显示我们选择的数据库的连接字符串,

Image 15

提供连接字符串的名称为WebApiDbEntities,然后单击Next。

Image 16

选择所有数据库对象,选中所有复选框,并为模型提供一个名称。我给它命名为WebApiDbModel。

完成此向导后,您将在datamodel项目中准备好模式,如下所示,

Image 17

我们已经使用实体框架实现了我们的模式。但剩下的工作很少。我们需要数据上下文类和实体,通过它们与数据库通信。

那么,我们进入下一个步骤。

步骤3:单击Visual Studio中的工具并打开扩展管理器。我们需要为我们的数据模型获得db上下文生成器。我们还可以通过在edmx视图中右键单击默认的代码生成项来实现,并添加代码生成项,但这将生成对象上下文类,而且比db上下文更重。我想要创建轻量级的db上下文类,所以我们将使用extension manager添加一个包,然后创建一个db上下文类。

Image 18

在网上搜索实体框架数据库上下文生成器,并选择一个为EF 5。x像下面,

Image 19

我猜你需要重新启动Visual studio来把它放到你的模板中。

步骤4:现在右键单击.edmx文件模式设计器并选择“添加代码生成项目..”。

Image 20

步骤5:现在您将看到我们已经获得了我们添加的扩展的模板,选择这个EF 5。DbContext生成器并单击Add。

Image 21

添加这个之后,我们将获得db上下文类及其属性,这个类负责我们需要执行的所有数据库事务,因此我们的结构如下所示,

Image 22

哇,我们以错误告终。但我们有了db上下文类和实体模型,你可以在DataModel项目中看到它们。错误吗?不用担心,只是我们在项目中没有引用实体框架。我们马上就做。

步骤6:打开Tools ->包管理器-包管理器控制台。您将在Visual studio的左下角获得控制台。

Image 23

选择dataModel项目并编写命令“install - package EntityFramework -Version 5.0.0”来在我们的dataModel项目中安装EntityFramework 5。

Image 24

按回车。所有的错误都得到了解决。

通用存储库和工作单元

您可以从我的文章中阅读关于存储库模式和创建存储库的详细信息:http://www.codeproject.com/articles/631668/learing-mvc-partrepository-patter-in-mvc-app。

为了列出存储库模式的好处,

  • 它集中了数据逻辑或Web服务访问逻辑。
  • 它为单元测试提供了替换点。
  • 它提供了一种灵活的体系结构,可以随着应用程序总体设计的发展而进行调整。

我们将创建一个适用于所有实体的通用存储库。在大型项目中,为每个实体创建存储库可能会导致大量重复的代码。要创建通用存储库,请遵循以下步骤:http://www.codeproject.com/articles/640294/learing-mvc-partgeneric-repository-pattern -in

步骤1:在DataModel项目中添加一个名为GenericRepository的文件夹,并在该文件夹中添加一个名为GenericRepository的类。将以下代码添加到该类中,该服务器作为将与databse交互的所有实体的基于模板的通用代码,

#region Using Namespaces...

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;

#endregion

namespace DataModel.GenericRepository
{
    /// <summary>
    /// Generic Repository class for Entity Operations
    /// </summary>
    /// <typeparamname="TEntity"></typeparam>
    public class GenericRepository<TEntity> where TEntity : class
    {
        #region Private member variables...
        internal WebApiDbEntities Context;
        internal DbSet<TEntity> DbSet;
        #endregion

        #region Public Constructor...
        /// <summary>
        /// Public Constructor,initializes privately declared local variables.
        /// </summary>
        /// <paramname="context"></param>
        public GenericRepository(WebApiDbEntities context)
        {
            this.Context = context;
            this.DbSet = context.Set<TEntity>();
        }
        #endregion

        #region Public member methods...

        /// <summary>
        /// generic Get method for Entities
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> Get()
        {
            IQueryable<TEntity> query = DbSet;
            return query.ToList();
        }

        /// <summary>
        /// Generic get method on the basis of id for Entities.
        /// </summary>
        /// <paramname="id"></param>
        /// <returns></returns>
        public virtual TEntity GetByID(object id)
        {
            return DbSet.Find(id);
        }

        /// <summary>
        /// generic Insert method for the entities
        /// </summary>
        /// <paramname="entity"></param>
        public virtual void Insert(TEntity entity)
        {
            DbSet.Add(entity);
        }

        /// <summary>
        /// Generic Delete method for the entities
        /// </summary>
        /// <paramname="id"></param>
        public virtual void Delete(object id)
        {
            TEntity entityToDelete = DbSet.Find(id);
            Delete(entityToDelete);
        }

        /// <summary>
        /// Generic Delete method for the entities
        /// </summary>
        /// <paramname="entityToDelete"></param>
        public virtual void Delete(TEntity entityToDelete)
        {
            if (Context.Entry(entityToDelete).State == EntityState.Detached)
            {
                DbSet.Attach(entityToDelete);
            }
            DbSet.Remove(entityToDelete);
        }

        /// <summary>
        /// Generic update method for the entities
        /// </summary>
        /// <paramname="entityToUpdate"></param>
        public virtual void Update(TEntity entityToUpdate)
        {
            DbSet.Attach(entityToUpdate);
            Context.Entry(entityToUpdate).State = EntityState.Modified;
        }

        /// <summary>
        /// generic method to get many record on the basis of a condition.
        /// </summary>
        /// <paramname="where"></param>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> GetMany(Func<TEntity, bool> where)
        {
            return DbSet.Where(where).ToList();
        }

        /// <summary>
        /// generic method to get many record on the basis of a condition but query able.
        /// </summary>
        /// <paramname="where"></param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> GetManyQueryable(Func<TEntity, bool> where)
        {
            return DbSet.Where(where).AsQueryable();
        }

        /// <summary>
        /// generic get method , fetches data for the entities on the basis of condition.
        /// </summary>
        /// <paramname="where"></param>
        /// <returns></returns>
        public TEntity Get(Func<TEntity, Boolean> where)
        {
            return DbSet.Where(where).FirstOrDefault<TEntity>();
        }

        /// <summary>
        /// generic delete method , deletes data for the entities on the basis of condition.
        /// </summary>
        /// <paramname="where"></param>
        /// <returns></returns>
        public void Delete(Func<TEntity, Boolean> where)
        {
            IQueryable<TEntity> objects = DbSet.Where<TEntity>(where).AsQueryable();
            foreach (TEntity obj in objects)
                DbSet.Remove(obj);
        }

        /// <summary>
        /// generic method to fetch all the records from db
        /// </summary>
        /// <returns></returns>
        public virtual IEnumerable<TEntity> GetAll()
        {
            return DbSet.ToList();
        }

        /// <summary>
        /// Inclue multiple
        /// </summary>
        /// <paramname="predicate"></param>
        /// <paramname="include"></param>
        /// <returns></returns>
        public IQueryable<TEntity> GetWithInclude(
            System.Linq.Expressions.Expression<Func<TEntity, 
            bool>> predicate, params string[] include)
        {
            IQueryable<TEntity> query = this.DbSet;
            query = include.Aggregate(query, (current, inc) => current.Include(inc));
            return query.Where(predicate);
        }

        /// <summary>
        /// Generic method to check if entity exists
        /// </summary>
        /// <paramname="primaryKey"></param>
        /// <returns></returns>
        public bool Exists(object primaryKey)
        {
            return DbSet.Find(primaryKey) != null;
        }

        /// <summary>
        /// Gets a single record by the specified criteria (usually the unique identifier)
        /// </summary>
        /// <paramname="predicate">Criteria to match on</param>
        /// <returns>A single record that matches the specified criteria</returns>
        public TEntity GetSingle(Func<TEntity, bool> predicate)
        {
            return DbSet.Single<TEntity>(predicate);
        }

        /// <summary>
        /// The first record matching the specified criteria
        /// </summary>
        /// <paramname="predicate">Criteria to match on</param>
        /// <returns>A single record containing the first record matching the specified criteria</returns>
        public TEntity GetFirst(Func<TEntity, bool> predicate)
        {
            return DbSet.First<TEntity>(predicate);
        }


        #endregion
    }
}

工作单元

同样,我不会详细解释什么是工作单元。你可以谷歌的理论或遵循我现有的文章MVC与工作单元。

提醒一下,从我现有的文章,工作单元的重要职责是,

  • 管理事务。
  • 对数据库的插入、删除和更新进行排序。
  • 防止重复更新。在一个工作单元对象的单一使用中,代码的不同部分可能会将同一个Invoice对象标记为已更改,但是工作类的单元将只向数据库发出一个更新命令。

使用工作单元模式的价值在于将代码的其余部分从这些关注点中解放出来,这样您就可以将精力集中在业务逻辑上。

步骤1:创建一个名为UnitOfWork的文件夹,向这个名为UnitOfWork.cs的文件夹添加一个类,

为我们得到的所有三个实体添加GenericRepository属性。这个类还实现了IDisposable接口,它的方法Dispose释放连接和对象。课程内容如下,

#region Using Namespaces...

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Data.Entity.Validation;
using DataModel.GenericRepository;

#endregion

namespace DataModel.UnitOfWork
{
    /// <summary>
    /// Unit of Work class responsible for DB transactions
    /// </summary>
    public class UnitOfWork : IDisposable
    {
        #region Private member variables...

        private WebApiDbEntities _context = null;
        private GenericRepository<User> _userRepository;
        private GenericRepository<Product> _productRepository;
        private GenericRepository<Token> _tokenRepository;
        #endregion

        public UnitOfWork()
        {
            _context = new WebApiDbEntities();
        }

        #region Public Repository Creation properties...

        /// <summary>
        /// Get/Set Property for product repository.
        /// </summary>
        public GenericRepository<Product> ProductRepository
        {
            get
            {
                if (this._productRepository == null)
                    this._productRepository = new GenericRepository<Product>(_context);
                return _productRepository;
            }
        }

        /// <summary>
        /// Get/Set Property for user repository.
        /// </summary>
        public GenericRepository<User> UserRepository
        {
            get
            {
                if (this._userRepository == null)
                    this._userRepository = new GenericRepository<User>(_context);
                return _userRepository;
            }
        }

        /// <summary>
        /// Get/Set Property for token repository.
        /// </summary>
        public GenericRepository<Token> TokenRepository
        {
            get
            {
                if (this._tokenRepository == null)
                    this._tokenRepository = new GenericRepository<Token>(_context);
                return _tokenRepository;
            }
        }
        #endregion

        #region Public member methods...
        /// <summary>
        /// Save method.
        /// </summary>
        public void Save()
        {
            try
            {
                _context.SaveChanges();
            }
            catch (DbEntityValidationException e)
            {

                var outputLines = new List<string>();
                foreach (var eve in e.EntityValidationErrors)
                {
                    outputLines.Add(string.Format(
                        "{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:", DateTime.Now, 
                        eve.Entry.Entity.GetType().Name, eve.Entry.State));
                    foreach (var ve in eve.ValidationErrors)
                    {
                        outputLines.Add(string.Format("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage));
                    }
                }
                System.IO.File.AppendAllLines(@"C:\errors.txt", outputLines);

                throw e;
            }

        }

        #endregion

        #region Implementing IDiosposable...

        #region private dispose variable declaration...
        private bool disposed = false; 
        #endregion

        /// <summary>
        /// Protected Virtual Dispose method
        /// </summary>
        /// <paramname="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    Debug.WriteLine("UnitOfWork is being disposed");
                    _context.Dispose();
                }
            }
            this.disposed = true;
        }

        /// <summary>
        /// Dispose method
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        } 
        #endregion
    }
}

现在我们已经完全建立了数据访问层,我们的项目结构如下所示,

Image 25

设置业务实体

请记住,我们创建了一个business entities项目。您可能想知道,我们已经有了与数据库交互的数据库实体,那么为什么还需要业务实体呢?答案很简单,我们试图遵循一种适当的通信结构,并且永远不会想要将数据库实体公开给终端客户端,在我们的例子中是Web API,它涉及很多风险。黑客可能会操纵这些细节并访问您的数据库。相反,我们将在业务逻辑层中使用数据库实体,并将业务实体用作传输对象,以便在业务逻辑和Web API项目之间进行通信。因此,业务实体可能有不同的名称,但它们的属性与数据库实体相同。在我们的案例中,我们将在我们的BusinessEntity项目中添加相同名称的业务实体类附录词“实体”。所以我们会有以下三个类,

Image 26

产品的实体

public class ProductEntity
    {
        public int ProductId { get; set; }
        public string ProductName { get; set; }
    }

令牌的实体

public class TokenEntity
    {
        public int TokenId { get; set; }
        public int UserId { get; set; }
        public string AuthToken { get; set; }
        public System.DateTime IssuedOn { get; set; }
        public System.DateTime ExpiresOn { get; set; }
    }

用户实体

public class UserEntity
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Name { get; set; }
    }

设立业务服务项目

向名为BusinessServices的解决方案添加一个新的类库。此层将作为我们的业务逻辑层。请注意,我们可以使用我们的API控制器来编写业务逻辑,但我试图在一个额外的层隔离我的业务逻辑,以便如果将来我想使用WCF、MVC、ASP。NET Web页面或任何其他应用程序作为我的表示层,然后我可以轻松地在其中集成我的业务逻辑层。

我们将使这个层成为可测试的,因此我们需要在其中创建一个接口,并声明需要在product表上执行的凝乳操作。在继续之前,将BusinessEntities项目和DataModel项目的引用添加到这个新创建的项目中

步骤1:创建一个名为IProductServices的接口,并为凝乳操作方法添加以下代码,

using System.Collections.Generic;
using BusinessEntities;

namespace BusinessServices
{
    /// <summary>
    /// Product Service Contract
    /// </summary>
    public interface IProductServices
    {
        ProductEntity GetProductById(int productId);
        IEnumerable<ProductEntity> GetAllProducts();
        int CreateProduct(ProductEntity productEntity);
        bool UpdateProduct(int productId,ProductEntity productEntity);
        bool DeleteProduct(int productId);
    }
}

步骤2:创建一个类来实现这个接口。

该类包含一个私有变量UnitOfWork和一个初始化该变量的构造函数,

private readonly UnitOfWork _unitOfWork;

  /// <summary>
  /// Public constructor.
  /// </summary>
  public ProductServices()
  {
      _unitOfWork = new UnitOfWork();
  }

我们已经决定不向Web API项目公开我们的db实体,所以我们需要一些东西来将db实体数据映射到我的业务实体类。我们将利用自动机器人。你可以在我的这篇文章中读到关于AutoMapper的内容。

步骤3:只需右键单击项目->扩展经理,搜索自动程序在网上仓库和添加到商业服务项目,

Image 27

步骤4:在ProductServices类中实现方法,

将以下代码添加到类中,

using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using AutoMapper;
using BusinessEntities;
using DataModel;
using DataModel.UnitOfWork;

namespace BusinessServices
{
    /// <summary>
    /// Offers services for product specific CRUD operations
    /// </summary>
    public class ProductServices:IProductServices
    {
        private readonly UnitOfWork _unitOfWork;

        /// <summary>
        /// Public constructor.
        /// </summary>
        public ProductServices()
        {
            _unitOfWork = new UnitOfWork();
        }

        /// <summary>
        /// Fetches product details by id
        /// </summary>
        /// <paramname="productId"></param>
        /// <returns></returns>
        public BusinessEntities.ProductEntity GetProductById(int productId)
        {
            var product = _unitOfWork.ProductRepository.GetByID(productId);
            if (product != null)
            {
                Mapper.CreateMap<Product, ProductEntity>();
                var productModel = Mapper.Map<Product, ProductEntity>(product);
                return productModel;
            }
            return null;
        }

        /// <summary>
        /// Fetches all the products.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()
        {
            var products = _unitOfWork.ProductRepository.GetAll().ToList();
            if (products.Any())
            {
                Mapper.CreateMap<Product, ProductEntity>();
                var productsModel = Mapper.Map<List<Product>, List<ProductEntity>>(products);
                return productsModel;
            }
            return null;
        }

        /// <summary>
        /// Creates a product
        /// </summary>
        /// <paramname="productEntity"></param>
        /// <returns></returns>
        public int CreateProduct(BusinessEntities.ProductEntity productEntity)
        {
            using (var scope = new TransactionScope())
            {
                var product = new Product
                {
                    ProductName = productEntity.ProductName
                };
                _unitOfWork.ProductRepository.Insert(product);
                _unitOfWork.Save();
                scope.Complete();
                return product.ProductId;
            }
        }

        /// <summary>
        /// Updates a product
        /// </summary>
        /// <paramname="productId"></param>
        /// <paramname="productEntity"></param>
        /// <returns></returns>
        public bool UpdateProduct(int productId, BusinessEntities.ProductEntity productEntity)
        {
            var success = false;
            if (productEntity != null)
            {
                using (var scope = new TransactionScope())
                {
                    var product = _unitOfWork.ProductRepository.GetByID(productId);
                    if (product != null)
                    {
                        product.ProductName = productEntity.ProductName;
                        _unitOfWork.ProductRepository.Update(product);
                        _unitOfWork.Save();
                        scope.Complete();
                        success = true;
                    }
                }
            }
            return success;
        }

        /// <summary>
        /// Deletes a particular product
        /// </summary>
        /// <paramname="productId"></param>
        /// <returns></returns>
        public bool DeleteProduct(int productId)
        {
            var success = false;
            if (productId > 0)
            {
                using (var scope = new TransactionScope())
                {
                    var product = _unitOfWork.ProductRepository.GetByID(productId);
                    if (product != null)
                    {

                        _unitOfWork.ProductRepository.Delete(product);
                        _unitOfWork.Save();
                        scope.Complete();
                        success = true;
                    }
                }
            }
            return success;
        }
    }
}

让我来解释一下代码的概念。我们有5种方法,

    通过id获取产品(GetproductById):我们调用repository来通过id获取产品。id作为一个参数从调用方法到那个服务方法。它从数据库返回产品实体。注意,它不会返回确切的db实体,相反,我们将使用AutoMapper将其映射到我们的业务实体,并将其返回到调用方法。 隐藏,复制代码/ / / & lt; summary> ///按id获取产品详细信息 / / / & lt; / summary> / / / & lt;参数name = " productId祝辞& lt; / param> / / / & lt; returns> & lt; / returns> 公共businessentity。ProductEntity GetProductById (int productId) { var product = _unitOfWork.ProductRepository.GetByID(productId); if (product != null) { ProductEntity> Mapper.CreateMap<产品;(); var productModel = Mapper.Map<Product, ProductEntity>(Product); 返回productModel; } 返回null; } 从database (GetAllProducts)获取所有产品:该方法返回驻留在database中的所有产品,我们再次使用AutoMapper映射列表并返回。 隐藏,复制代码/ / / & lt; summary> ///取回所有产品。 / / / & lt; / summary> / / / & lt; returns> & lt; / returns> 公共IEnumerable< BusinessEntities.ProductEntity>GetAllProducts () { var products = _unitOfWork.ProductRepository.GetAll().ToList(); 如果(products.Any ()) { ProductEntity> Mapper.CreateMap<产品;(); var productsModel = Mapper.Map< Product>, List<ProductEntity> (products); 返回productsModel; } 返回null; } 创建一个新产品(CreateProduct):该方法以产品BusinessEntity作为参数,创建一个实际数据库实体的新对象,并使用工作单元插入它。 隐藏,复制代码/ / / & lt; summary> ///创建产品 / / / & lt; / summary> / / / & lt;参数name = " productEntity祝辞& lt; / param> / / / & lt; returns> & lt; / returns> 公共int CreateProduct (businessentity。ProductEntity ProductEntity) { 使用(var scope = new TransactionScope()) { var product =新产品 { ProductName = productEntity.ProductName }; _unitOfWork.ProductRepository.Insert(产品); _unitOfWork.Save (); scope.Complete (); 返回product.ProductId; } }

我想你现在可以写更新和删除方法了。所以我在写完整类的代码,

using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using AutoMapper;
using BusinessEntities;
using DataModel;
using DataModel.UnitOfWork;

namespace BusinessServices
{
    /// <summary>
    /// Offers services for product specific CRUD operations
    /// </summary>
    public class ProductServices:IProductServices
    {
        private readonly UnitOfWork _unitOfWork;

        /// <summary>
        /// Public constructor.
        /// </summary>
        public ProductServices()
        {
            _unitOfWork = new UnitOfWork();
        }

        /// <summary>
        /// Fetches product details by id
        /// </summary>
        /// <paramname="productId"></param>
        /// <returns></returns>
        public BusinessEntities.ProductEntity GetProductById(int productId)
        {
            var product = _unitOfWork.ProductRepository.GetByID(productId);
            if (product != null)
            {
                Mapper.CreateMap<Product, ProductEntity>();
                var productModel = Mapper.Map<Product, ProductEntity>(product);
                return productModel;
            }
            return null;
        }

        /// <summary>
        /// Fetches all the products.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<BusinessEntities.ProductEntity> GetAllProducts()
        {
            var products = _unitOfWork.ProductRepository.GetAll().ToList();
            if (products.Any())
            {
                Mapper.CreateMap<Product, ProductEntity>();
                var productsModel = Mapper.Map<List<Product>, List<ProductEntity>>(products);
                return productsModel;
            }
            return null;
        }

        /// <summary>
        /// Creates a product
        /// </summary>
        /// <paramname="productEntity"></param>
        /// <returns></returns>
        public int CreateProduct(BusinessEntities.ProductEntity productEntity)
        {
            using (var scope = new TransactionScope())
            {
                var product = new Product
                {
                    ProductName = productEntity.ProductName
                };
                _unitOfWork.ProductRepository.Insert(product);
                _unitOfWork.Save();
                scope.Complete();
                return product.ProductId;
            }
        }

        /// <summary>
        /// Updates a product
        /// </summary>
        /// <paramname="productId"></param>
        /// <paramname="productEntity"></param>
        /// <returns></returns>
        public bool UpdateProduct(int productId, BusinessEntities.ProductEntity productEntity)
        {
            var success = false;
            if (productEntity != null)
            {
                using (var scope = new TransactionScope())
                {
                    var product = _unitOfWork.ProductRepository.GetByID(productId);
                    if (product != null)
                    {
                        product.ProductName = productEntity.ProductName;
                        _unitOfWork.ProductRepository.Update(product);
                        _unitOfWork.Save();
                        scope.Complete();
                        success = true;
                    }
                }
            }
            return success;
        }

        /// <summary>
        /// Deletes a particular product
        /// </summary>
        /// <paramname="productId"></param>
        /// <returns></returns>
        public bool DeleteProduct(int productId)
        {
            var success = false;
            if (productId > 0)
            {
                using (var scope = new TransactionScope())
                {
                    var product = _unitOfWork.ProductRepository.GetByID(productId);
                    if (product != null)
                    {

                        _unitOfWork.ProductRepository.Delete(product);
                        _unitOfWork.Save();
                        scope.Complete();
                        success = true;
                    }
                }
            }
            return success;
        }
    }
}

在业务服务级别完成的工作。让我们转到API控制器来调用这些方法。

设置WebAPI项目

步骤1:

只要在WebAPI项目中添加BusinessEntity和BusinessService的引用,我们的架构就会变成这样,

Image 28

步骤2:在控制器文件夹中添加一个新的WebAPI控制器。右键单击控制器文件夹并添加一个新控制器。

Image 29

我们得到一个如下的控制器,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace WebApi.Controllers
{
    public class ProductController : ApiController
    {
        // GET api/product
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/product/5
        public string Get(int id)
        {
            return "value";
        }

        // POST api/product
        public void Post([FromBody]string value)
        {
        }

        // PUT api/product/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/product/5
        public void Delete(int id)
        {
        }
    }
}

我们得到HTTP动词作为方法名。Web API足够智能,可以使用谓词本身的名称识别请求。在我们的例子中,我们正在执行CRUD操作,所以我们不需要更改方法的名称,我们只需要这个。我们只需要在这些方法中编写调用逻辑。在本系列的后续文章中,我们将了解如何定义新路由,并为这些路由提供我们所选择的方法名称。

步骤3:添加调用业务服务方法的逻辑,只要创建一个业务服务的对象并调用其各自的方法,我们的控制器类就变成这样:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using BusinessEntities;
using BusinessServices;

namespace WebApi.Controllers
{
    public class ProductController : ApiController
    {

        private readonly IProductServices _productServices;

         #region Public Constructor

        /// <summary>
        /// Public constructor to initialize product service instance
        /// </summary>
        public ProductController()
        {
            _productServices =new ProductServices();
        }

        #endregion

        // GET api/product
        public HttpResponseMessage Get()
        {
            var products = _productServices.GetAllProducts();
            if (products != null)
            {
                var productEntities = products as List<ProductEntity> ?? products.ToList();
                if (productEntities.Any())
                    return Request.CreateResponse(HttpStatusCode.OK, productEntities);
            }
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Products not found");
        }

        // GET api/product/5
        public HttpResponseMessage Get(int id)
        {
            var product = _productServices.GetProductById(id);
            if (product != null)
                return Request.CreateResponse(HttpStatusCode.OK, product);
            return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No product found for this id");
        }

        // POST api/product
        public int Post([FromBody] ProductEntity productEntity)
        {
            return _productServices.CreateProduct(productEntity);
        }

        // PUT api/product/5
        public bool Put(int id, [FromBody]ProductEntity productEntity)
        {
            if (id  > 0)
            {
                return _productServices.UpdateProduct(id, productEntity);
            }
            return false;
        }

        // DELETE api/product/5
        public bool Delete(int id)
        {
            if (id > 0)
                return _productServices.DeleteProduct(id);
            return false;
        }
    }
}

运行这个应用程序,我们得到,

Image 30

现在如何测试API呢?我们没有客户。伙计们,我们现在不会编写客户端来测试它。我们将添加一个包来完成所有工作。

只要去管理Nuget包,通过右击WebAPI项目,并输入WebAPITestClient在搜索框的在线包,

Image 31

您将获得“一个用于ASP的简单测试客户端”。NET Web API”,只要添加它。你将在以下区域得到一个帮助控制器->如下图所示,

Image 32

运行应用程序

在运行应用程序之前,我将一些测试数据放入产品表中。

按下F5,你会得到和之前一样的页面,只是在它的url中追加"/help",你会得到测试客户端,

Image 33

您可以通过单击每个服务来测试它。单击服务链接后,您将被重定向到测试该特定服务的服务页面。在这个页面的右下角有一个按钮测试API,只要按下那个按钮就可以测试你的服务,

Test API

,

,

,

,

,

,

,

,

,

,

,

,

,

,

GetAllProduct服务,

Image 35

为了创造新产品,

Image 36

在数据库中,我们有新产品,

Image 37

更新产品:

Image 38

我们进入数据库,

Image 39

删除产品:

Image 40

在数据库:

Image 41

工作。

Image 42

设计缺陷

    架构是紧密耦合的。需要有IOC(控制反转)。我们无法确定自己的路线。没有异常处理和日志记录。任何单位tetsts。

结论

我们现在知道了如何使用n层架构创建WebAPI和执行CRUD操作。

但是这个设计还是有一些缺陷。在接下来的两篇文章中,我将解释如何使用依赖注入原理使系统松散耦合。我们还将覆盖所有的设计缺陷,使我们的设计更好更强。到那时你还可以从GitHub下载源代码。

我的其他系列文章:

MVC: http://www.codeproject.com/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu

OOP: http://www.codeproject.com/Articles/771455/Diving-in-OOP-Day-Polymorphism-and-Inheritance-Ear

本文转载于:http://www.diyabc.com/frontweb/news14900.html

posted @ 2020-08-12 04:46  Dincat  阅读(124)  评论(0编辑  收藏  举报