ASP.NET Mvc实用框架(一)Ioc、仓储模式和单元工作模式

Framework.EF

首先看一下这个类库:

Extended文件夹存放的是EntityFramework.Extensions这个插件的源代码,没有别的原因,就是本人觉得这个插件挺好的,每次省的下载而已

IDependency:用于依赖注入的接口

IRepository和Repository:用于仓储模式

IUnitOfWork和UnitOfWork:用于单元工作模式

Page:分页实体

1、什么是依赖注入?

记得第一次接触依赖注入的时候是在我大二暑假自己出去实习的时候,当时带我的人让我看一看依赖注入这个东西,因为项目用到了,我愣是看了一个星期,各种熬夜、百度、问人,还没明白怎么回事?现在想想挺搞笑的。其实这个东西需要一定的项目经验理解起来会方便些,其实我们平时在研究问题的时候,当你实在解决不了的时候,不妨先记下来,有空的时候多看看,总有一天会豁然开朗的。扯远了。。。

其实依赖注入我跟喜欢把它当成控制反转,这样更贴切,为什么呢?举个例子:三层架构中的BLL层,我们通常定义一个接口层让它继承,在使用的时候,直接 接口申明 - BLL实例化,而控制反转的思想是将它们的顺序颠倒过来,也就是说我们不再关注接口到底是怎么实现的了,这样就实现了解耦合,在我看来这是一项伟大的创新。而实现控制反转这个思想当然是依赖注入容器,当然插件有很多,我用过Unity和Autofac,个人感觉还是第三方的Autofac强大,性能相对于Unity而言十分的明显

using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;
using Framework.EF;

namespace Test.Web
{
    public class AutofacConfig
    {
        /// <summary>
        /// 初始化
        /// </summary>
        public static void Initialise()
        {
            var builder = RegisterService();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(builder.Build()));
        }

        /// <summary>
        /// 注入实现
        /// </summary>
        /// <returns></returns>
        private static ContainerBuilder RegisterService()
        {
            var builder = new ContainerBuilder();

            var baseType = typeof(IDependency);

            //扫描IService和Service相关的程序集

            var assemblys = BuildManager.GetReferencedAssemblies().Cast<Assembly>()
                .Where(m => m.FullName.Contains("Test")).ToList();

            builder.RegisterControllers(assemblys.ToArray());

            //自动注入
            builder.RegisterAssemblyTypes(assemblys.ToArray())
                   .Where(t => baseType.IsAssignableFrom(t) && t != baseType)
                   .AsImplementedInterfaces().InstancePerLifetimeScope();
            return builder;
        }
    }
}

Autofac自动注入

2、什么是仓储模式?

关于这方面的介绍有很多,我说说我的理解,通常我们所用的ORM都会自己有一套CURD相关的方法,比方说EF中的dbcontext,NH中的session,其实我们会发现在仓储模式中又会定义一套CURD相关的方法,有的人会问:这TM的不是重复了吗?当时我也是这么想的,其实不然,如果你的ORM永远不可能变的话,那么你可以不用仓储模式,但是这种情况概率基本不可能发生,谁也无法保证你的领导脑袋会不会抽风,万一,就万一哪天需要更换底层的东西的呢?如果你使用了仓储模式的话,这样更换底层就十分的方便,只需要将仓储中的实现挨个重写一遍,这就是好处。

/* ===============================================================
 * 创 建 者:wms
 * 创建日期:2016/11/18 17:06:34
 * 功能描述:仓储接口
 * ===============================================================*/

namespace Framework.EF
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>
    /// 仓储接口
    /// </summary>
    public interface IRepository<TEntity> : IDisposable where TEntity : class
    {

        #region 删除操作

        /// <summary>
        /// 删除实体
        /// </summary>
        /// <param name="entityToDelete">要删除的实体</param>
        void Delete(TEntity entityToDelete);

        /// <summary>
        /// 删除/批量删除
        /// </summary>
        /// <param name="predicate">表达式</param>
        /// <returns></returns>
        int Delete(Expression<Func<TEntity, bool>> predicate);

        #endregion

        #region 更新操作

        /// <summary>
        /// 批量更新
        /// <code><![CDATA[
        /// var db = new TrackerContext();
        /// string emailDomain = "@test.com";
        /// int count = db.Users.Update(
        ///   u => u.Email.EndsWith(emailDomain),
        ///   u => new User { IsApproved = false, LastActivityDate = DateTime.Now });
        /// ]]></code>
        /// </summary>
        /// <param name="filterExpression">条件筛选表达式</param>
        /// <param name="updateExpression">更新表达式</param>
        int Update(Expression<Func<TEntity, bool>> filterExpression,
            Expression<Func<TEntity, TEntity>> updateExpression);
        #endregion

        #region 新增操作

        /// <summary>
        /// 新增
        /// </summary>
        /// <param name="entity">要新增的实体</param>
        TEntity Insert(TEntity entity);
        #endregion

        #region 查询操作

        #region Count
        /// <summary>
        /// Count查询
        /// </summary>
        /// <returns></returns>
        int Count();
        /// <summary>
        /// Count查询
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        int Count(Expression<Func<TEntity, bool>> predicate);

        #endregion

        #region 单个查询

        /// <summary>
        /// 取查询结果中的第一条(为空抛异常)
        /// </summary>
        /// <returns></returns>
        TEntity First();

        /// <summary>
        /// 取查询结果中的第一条(为空抛异常)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        TEntity First(Expression<Func<TEntity, bool>> predicate);
        /// <summary>
        /// 取查询结果中的第一条(为空返回默认值)
        /// </summary>
        /// <returns></returns>
        TEntity FirstOrDefault();
        /// <summary>
        /// 取查询结果中的第一条(为空返回默认值)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);

        /// <summary>
        /// 取一条数据(为空抛异常)
        /// </summary>
        /// <returns></returns>
        TEntity Single();

        /// <summary>
        /// 取一条数据(为空抛异常)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        TEntity Single(Expression<Func<TEntity, bool>> predicate);

        /// <summary>
        /// 取一条数据(没有为空)
        /// </summary>
        /// <returns></returns>
        TEntity SingleOrDefault();

        /// <summary>
        /// 取一条数据(没有为空)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate);

        #endregion

        /// <summary>
        /// 获取列表(延迟加载)
        /// </summary>
        /// <param name="predicate">表达式树</param>
        /// <returns></returns>
        IQueryable<TEntity> LoadEntities(Expression<Func<TEntity, bool>> predicate);

        #region 分页查询

        /// <summary>
        /// 分页查询
        /// </summary>
        /// <typeparam name="TKey">排序类型</typeparam>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">每页大小</param>
        /// <param name="isAsc">是否升序排列</param>
        /// <param name="keySelector">排序表达式</param>
        /// <returns></returns>
        IPage<TEntity> Page<TKey>(int pageIndex, int pageSize, bool isAsc, Expression<Func<TEntity, TKey>> keySelector);

        /// <summary>
        /// 分页查询
        /// </summary>
        /// <typeparam name="TKey">排序类型</typeparam>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">每页大小</param>
        /// <param name="isAsc">是否升序排列</param>
        /// <param name="predicate">条件表达式</param>
        /// <param name="keySelector">排序表达式</param>
        /// <returns></returns>
        IPage<TEntity> Page<TKey>(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, bool isAsc,
            Expression<Func<TEntity, TKey>> keySelector);

        #endregion

        #endregion

        /// <summary>
        /// 保存修改
        /// </summary>
        /// <returns></returns>
        int SaveChanges();
    }
}
/* ===============================================================
 * 创 建 者:wms
 * 创建日期:2016/11/18 17:13:56
 * 功能描述:仓储实现类
 * ===============================================================*/

using EntityFramework.Extensions;
using EntityFramework.Future;

namespace Framework.EF
{
    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>
    /// 仓储实现类
    /// </summary>
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// 上下文
        /// </summary>
        protected DbContext dbContext;

        /// <summary>
        /// DbSet
        /// </summary>
        protected DbSet<TEntity> dbSet;

        /// <summary>
        /// 无参构造函数《用于单独的Service直接使用》
        /// </summary>
        public Repository() { }

        /// <summary>
        /// 用于UnitOfWork
        /// </summary>
        /// <param name="dbContext">上下文</param>
        public Repository(DbContext dbContext)
        {
            this.dbContext = dbContext;
            this.dbSet = dbContext.Set<TEntity>();
        }

        #region 删除操作

        /// <summary>
        /// 删除操作
        /// </summary>
        /// <param name="entityToDelete"></param>
        public virtual void Delete(TEntity entityToDelete)
        {
            if (dbContext.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        /// <summary>
        /// 删除/批量删除
        /// </summary>
        /// <param name="predicate">表达式</param>
        /// <returns></returns>
        public virtual int Delete(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.Delete(predicate);
        }

        #endregion

        #region 更新操作

        /// <summary>
        /// 批量更新
        /// <code><![CDATA[
        /// var db = new TrackerContext();
        /// string emailDomain = "@test.com";
        /// int count = db.Users.Update(
        ///   u => u.Email.EndsWith(emailDomain),
        ///   u => new User { IsApproved = false, LastActivityDate = DateTime.Now });
        /// ]]></code>
        /// </summary>
        /// <param name="filterExpression">条件筛选表达式</param>
        /// <param name="updateExpression">更新表达式</param>
        public virtual int Update(Expression<Func<TEntity, bool>> filterExpression,
            Expression<Func<TEntity, TEntity>> updateExpression)
        {
            return this.dbSet.Update(filterExpression, updateExpression);
        }

        #endregion

        #region 新增操作

        /// <summary>
        /// 新增
        /// </summary>
        /// <param name="entity">要新增的实体</param>
        public virtual TEntity Insert(TEntity entity)
        {
            return this.dbSet.Add(entity);
        }
        #endregion

        #region 查询操作

        #region Count

        /// <summary>
        /// Count查询
        /// </summary>
        /// <returns></returns>
        public virtual int Count()
        {
            return this.dbSet.Count();
        }

        /// <summary>
        /// Count查询
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        public virtual int Count(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.Count();
        }

        #endregion

        #region 单个查询

        /// <summary>
        /// 取查询结果中的第一条(为空抛异常)
        /// </summary>
        /// <returns></returns>
        public virtual TEntity First()
        {
            return this.dbSet.First();
        }

        /// <summary>
        /// 取查询结果中的第一条(为空抛异常)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        public virtual TEntity First(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.First(predicate);
        }
        /// <summary>
        /// 取查询结果中的第一条(为空返回默认值)
        /// </summary>
        /// <returns></returns>
        public virtual TEntity FirstOrDefault()
        {
            return this.dbSet.FirstOrDefault();
        }
        /// <summary>
        /// 取查询结果中的第一条(为空返回默认值)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.FirstOrDefault(predicate);
        }

        /// <summary>
        /// 取一条数据(为空抛异常)
        /// </summary>
        /// <returns></returns>
        public virtual TEntity Single()
        {
            return this.dbSet.Single();
        }

        /// <summary>
        /// 取一条数据(为空抛异常)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        public virtual TEntity Single(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.Single(predicate);
        }

        /// <summary>
        /// 取一条数据(没有为空)
        /// </summary>
        /// <returns></returns>
        public virtual TEntity SingleOrDefault()
        {
            return this.dbSet.SingleOrDefault();
        }

        /// <summary>
        /// 取一条数据(没有为空)
        /// </summary>
        /// <param name="predicate">条件表达式</param>
        /// <returns></returns>
        public virtual TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.SingleOrDefault(predicate);
        }

        #endregion

        /// <summary>
        /// 获取列表(延迟加载)
        /// </summary>
        /// <param name="predicate">表达式树</param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> LoadEntities(Expression<Func<TEntity, bool>> predicate)
        {
            return this.dbSet.Where(predicate);
        }

        #region 分页查询

        /// <summary>
        /// 分页查询
        /// </summary>
        /// <typeparam name="TKey">排序类型</typeparam>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">每页大小</param>
        /// <param name="isAsc">是否升序排列</param>
        /// <param name="keySelector">排序表达式</param>
        /// <returns></returns>
        public virtual IPage<TEntity> Page<TKey>(int pageIndex, int pageSize, bool isAsc, Expression<Func<TEntity, TKey>> keySelector)
        {
            return this.Page(pageIndex, pageSize, null, isAsc, keySelector);
        }

        /// <summary>
        /// 分页查询
        /// </summary>
        /// <typeparam name="TKey">排序类型</typeparam>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">每页大小</param>
        /// <param name="isAsc">是否升序排列</param>
        /// <param name="predicate">条件表达式</param>
        /// <param name="keySelector">排序表达式</param>
        /// <returns></returns>
        public virtual IPage<TEntity> Page<TKey>(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> predicate, bool isAsc,
            Expression<Func<TEntity, TKey>> keySelector)
        {
            if (pageIndex <= 0 && pageSize <= 0)
            {
                throw new Exception("pageIndex或pageSize不能小于等于0!");
            }
            IPage<TEntity> page = new Page<TEntity>()
            {
                PageIndex = pageIndex,
                PageSize = pageSize
            };
            int skip = (pageIndex - 1) * pageSize;
            if (predicate == null)
            {
                FutureCount fcount = this.dbSet.FutureCount();
                FutureQuery<TEntity> futureQuery = isAsc
                    ? this.dbSet.OrderBy(keySelector).Skip(skip).Take(pageSize).Future()
                    : this.dbSet.OrderByDescending(keySelector).Skip(skip).Take(pageSize).Future();
                page.TotalItems = fcount.Value;
                page.Items = futureQuery.ToList();
                page.TotalPages = page.TotalItems / pageSize;
                if ((page.TotalItems % pageSize) != 0) page.TotalPages++;
            }
            else
            {
                var queryable = this.dbSet.Where(predicate);
                FutureCount fcount = queryable.FutureCount();
                FutureQuery<TEntity> futureQuery = isAsc
                    ? queryable.OrderBy(keySelector).Skip(skip).Take(pageSize).Future()
                    : queryable.OrderByDescending(keySelector).Skip(skip).Take(pageSize).Future();
                page.TotalItems = fcount.Value;
                page.Items = futureQuery.ToList();
                page.TotalPages = page.TotalItems / pageSize;
                if ((page.TotalItems % pageSize) != 0) page.TotalPages++;
            }
            return page;
        }

        #endregion

        #endregion

        /// <summary>
        /// 资源释放
        /// </summary>
        public virtual void Dispose()
        {
            dbContext.Dispose();
        }

        /// <summary>
        /// 保存修改
        /// </summary>
        /// <returns></returns>
        public virtual int SaveChanges()
        {
            return this.dbContext.SaveChanges();
        }

    }
}

3、什么是单元工作模式?

其实就是统一资源,减少资源浪费而已,我们就拿EF来说,其实我发现有些人会在单元工作模式中定义了一些事务的操作方法,在我看来这是没有必要的,为什么呢?因为EF中的上下文本身就是具有事务性质的,我们只需要在单元工作模式中定义一个统一提交的方法即可,如下:

/* ==============================================================================
 * 创 建 者:wms
 * 创建日期:2016-11-18 16:55:25
 * 功能描述:单元工作模式接口
 * ==============================================================================*/

namespace Framework.EF
{
    using System;

    /// <summary>
    /// 单元工作模式
    /// </summary>
    public interface IUnitOfWork : IDisposable
    {
        /// <summary>
        /// 保存
        /// </summary>
        void SaveChanges();
    }
}
/* ==============================================================================
 * 创 建 者:wms
 * 创建日期:2016-11-18 16:55:49
 * 功能描述:单元工作模式
 * ==============================================================================*/

namespace Framework.EF
{
    using System;
    using System.Data.Entity;

    /// <summary>
    /// 单元工作模式
    /// </summary>
    public abstract class UnitOfWork : IUnitOfWork
    {
        /// <summary>
        ///
        /// </summary>
        /// <param name="dbContext"></param>
        protected UnitOfWork(DbContext dbContext)
        {
            this.DbContext = dbContext;
        }

        /// <summary>
        /// 共用上下文
        /// </summary>
        public DbContext DbContext { get; set; }

        /// <summary>
        /// 保存
        /// </summary>
        public void SaveChanges()
        {
            this.DbContext.SaveChanges();
        }

        private bool disposed = false;

        /// <summary>
        /// 资源释放
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    this.DbContext.Dispose();
                }
            }
            this.disposed = true;
        }

        /// <summary>
        /// 资源释放
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
}

本系列完成之后附上源代码

时间: 2024-12-20 00:43:55

ASP.NET Mvc实用框架(一)Ioc、仓储模式和单元工作模式的相关文章

新作《ASP.NET MVC 5框架揭秘》正式出版

ASP.NET MVC是一个建立在ASP.NET平台上基于MVC模式的Web开发框架,它提供了一种与Web Form完全不同的开发方式.ASP.NET Web Form借鉴了Windows Form基于控件和事件注册的编程模式,使Web应用的开发变得简单而快捷,但是它却使开发人员与Web的本质渐行渐远.ASP.NET MVC是一种回归,它使开发人员可以真正地面向Web进行编程,我们面对的不再是拖拉到Web页面的控件,而是整个HTTP请求和响应的流程. 这不是一本传统意义上的入门书籍 在<ASP

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(4)-业务逻辑层的封装

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(4)-业务逻辑层的封装 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2):数据库访问层的设计Demo    (3):面向接口编程 前言:前面几篇博客我们基本已经介绍完了搭建整个项目和数据库访问层以及一些业务逻辑层的实现,当然了,我们的数据库访问层这样还是可以在进行封装的,但是我到这里就行了吧,项目也不大,不需要那么麻烦的,那么我们今天开始介绍我们需要介绍的内容,那就是我

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(2)-数据库访问层的设计Demo

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(2)-数据库访问层的设计Demo ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) (1)框架搭建 前言:这篇博客我们继续来实现我的权限系列,这个博客一段时间也没有写了,重点是我在想还写不写,最终我决定还是写下去,因为我们是为了学习,当别人提出意见的时候,我们可以参考和采纳,但是我们不一定非要采纳,上几篇博客大家都说用CodeFirst来实现,是啊,现在基本很少有人用我的这种方法来实现了,都是用CodeF

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(6)- EF上下文实例管理

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(6)- EF上下文实例管理 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2):数据库访问层的设计Demo    (3):面向接口编程   (4 ):业务逻辑层的封装  (5):前台Jquery easyUI实现 前言:通过前面的五篇博客我们已经对权限系统的后台架构进行了详细的说明,那么我再前面的博客中也说到了我们的后台架构还会再改的,我准备这段时间我们继续完善我们的后台

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(7)-DBSession的封装

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(7)-DBSession的封装 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2):数据库访问层的设计Demo    (3):面向接口编程   (4 ):业务逻辑层的封装  (5):前台Jquery easyUI实现   (6):EF上下文实例管理 前言:上篇博客中我们重新对EF框架实现上下文进行了重新的操作,而且我们也建立了DbSession,使用CallContext

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(9)-TT模板的学习

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(9)-TT模板的学习 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2):数据库访问层的设计Demo    (3):面向接口编程   (4 ):业务逻辑层的封装  (5):前台Jquery easyUI实现   (6):EF上下文实例管理   (7):DBSession的封装   (8):DBSession线程内唯一 前言:前面我们基本已经完成了一个Demo,我们在后面的

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(3)-面向接口的编程

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(3)-面向接口的编程 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)  (1)框架搭建    (2):数据库访问层的设计Demo 前言:这篇博客在数据访问层的基础上面我们继续学习对这个Demo的开发,希望大家都好好理解一下这个Demo的架构,到最后我实现权限的时候我就简单的说一下搭建过程就OK了,因为这个Demo的思想就是按照权限的设计方式去设计的,下面我们开始介绍面向接口的编程思想,如果感觉好的话可以

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(8)-DbSession线程内唯一

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(8)-DbSession线程内唯一 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2):数据库访问层的设计Demo    (3):面向接口编程   (4 ):业务逻辑层的封装  (5):前台Jquery easyUI实现   (6):EF上下文实例管理   (7):DBSession的封装 前言:通过上篇博客我们完成了对DbSession的代码编写,DbSession就相

《ASP.NET MVC 5 框架揭秘》

<ASP.NET MVC 5 框架揭秘> 基本信息 作者: 蒋金楠 出版社:电子工业出版社 ISBN:9787121237812 上架时间:2014-8-1 出版日期:2014 年8月 开本:16开 页码:656 版次:1-1 所属分类:计算机 > 软件与程序设计 > .NET > ASP.NET 更多关于>>> <ASP.NET MVC 5 框架揭秘>   编辑推荐 如果你觉得自己对ASP.NET MVC 所知甚少,可利用本书来系统学习:如果你