转载

Portal.MVC —— nopcommerce的简化版

Portal.MVC 简介

项目是基于MVC4+EF,带有角色,权限,用户中心及账户相关(登录,注册,修改密码,找回密码等)等基本功能。参考的开源项目 nopcommerce ,这是一个电商架构的MVC项目,我对其进行了简化,之前主要是方便我自己搭建一些小的网站。包含前台和后台。

界面浏览

1.首页。这是前天晚上临时做出来的Logo和页面。不是真实案例,大家可以结合实际项目再修改。

Portal.MVC —— nopcommerce的简化版

2.登录框

Portal.MVC —— nopcommerce的简化版

2.注册框

Portal.MVC —— nopcommerce的简化版

3.邮箱密码找回,这个地方要用还需要配置邮箱服务器。

Portal.MVC —— nopcommerce的简化版

4.用户中心

Portal.MVC —— nopcommerce的简化版

4.后台管理,使用的Matrix Admin模板。这是一个很好用的模板,是衍生于Bootstrap。

Portal.MVC —— nopcommerce的简化版

用户管理,这里excel导出用的是NPIO。

Portal.MVC —— nopcommerce的简化版

不知道有没有提起园友的兴趣,下面我讲一下代码部分。

功能介绍

我没有做分太多的层级,这样比较直观,核心的部分在 Niqiu.Core 类库里面.分四个部分

Portal.MVC —— nopcommerce的简化版

  • Domain: 包含主要的领域模型,比如User,UserRole,PermissionRecord等
  • Helpers: 包含一些帮助类,比如xml,email
  • Mapping: 数据映射
  • Services: 服务部分的接口和实现

而网站部分重要的有一些可以复用的Attributes,AccountController等,比如UserLastActivityIpAttribute,会记录用户的Ip并更新最后访问时间。

下面介绍下2个我认为重要点的部分

Ninject依赖注入:

Nop中使用的是 Autofac ,并构建了一个强大的 EnginContext 管理所有的依赖注入项,在这个项目中我拿掉了这一部分,换成 Ninject 来完成IOC的工作。并不涉及这两个组件的优劣问题,而且我认为前者的功能更要强悍一些。Ninject是在 NinjectWebCommon 类中注册的.它在App_Start文件夹下。 如下:

 kernel.Bind<IPermissionservice>().To<Permissionservice>(); 

Ninject更详细的介绍请看我的博客: Ninject在MVC5中的使用 。在不能用构造函数的地方,可以用属性注入。

   [Inject]    public IWorkContext WorkContext { get; set; }    [Inject]   public IUserService UserService { get; set; } 

而有的Service需要用到HttpContext,这对象没有接口,是无法注册的,但可以通过HttpContextWrapper获得。

  public HttpContextBase HttpContext         {             get { return new HttpContextWrapper(System.Web.HttpContext.Current); }         } 
HttpContextBase 是一个抽象类,HttpContextWrapper是其派生成员。这两者都是.Net3.5之后才出现。

更多的解释大家可以看老赵的博客: 为什么是HttpContextBase而不是IHttpContext

领域设计

这个部分网上讨论的比较多,Nop是采用的单个仓库接口IRepository<T>,然后实现不同的服务。定义IDbContext,注入数据库对象。

IRepository<T>:

  public interface IRepository<T> where T:BaseEntity    {        /// <summary>        /// Gets the by id.        /// </summary>        /// <param name="id">The id.</param>        /// <returns>`0.</returns>        T GetById(object id);        /// <summary>        /// Inserts the specified entity.        /// </summary>        /// <param name="entity">The entity.</param>        void Insert(T entity);        /// <summary>        /// Inserts the specified entities.        /// </summary>        /// <param name="entities">The entities.</param>        void Insert(IEnumerable<T> entities);        /// <summary>        /// Updates the specified entity.        /// </summary>        /// <param name="entity">The entity.</param>        void Update(T entity);        /// <summary>        /// Deletes the specified entity.        /// </summary>        /// <param name="entity">The entity.</param>        void Delete(T entity);        /// <summary>        /// Gets the table.        /// </summary>        /// <value>The table.</value>        IQueryable<T> Table { get; }        /// <summary>        /// Gets the tables no tracking.        /// </summary>        /// <value>The tables no tracking.</value>        IQueryable<T> TableNoTracking { get; }    }  View Code

用EfRepository<T>实现这个接口

  public class EfRepository<T>:IRepository<T> where T:BaseEntity    {        #region Fields         private readonly IDbContext _context ;         private IDbSet<T> _entities;         #endregion        public EfRepository(IDbContext context)        {            if (context == null)            {                throw new ArgumentNullException("context");            }            _context = context;        }        #region property         protected virtual IDbSet<T> Entities        {            get            {                if (_entities == null)                {                    _entities = _context.Set<T>();                }                return _entities ?? (_entities = _context.Set<T>());                    // _entities ?? _entities = db.Set<T>();            }        }         /// <summary>        /// Gets a table        /// </summary>        public virtual IQueryable<T> Table        {            get            {                return Entities;            }        }         public IQueryable<T> TableNoTracking        {            get            {                return Entities.AsNoTracking();                            }        }          #endregion                public virtual T GetById(object id)        {            return Entities.Find(id);        }         public void Insert(T entity)        {            try            {                if(entity==null) throw new ArgumentException("entity");                Entities.Add(entity);                _context.SaveChanges();            }            catch (DbEntityValidationException dbEx)            {                GetException(dbEx);            }        }         /// <summary>        /// Insert entities        /// </summary>        /// <param name="entities">Entities</param>        public virtual void Insert(IEnumerable<T> entities)        {            try            {                if (entities == null)                    throw new ArgumentNullException("entities");                 foreach (var entity in entities)                    Entities.Add(entity);                 _context.SaveChanges();            }            catch (DbEntityValidationException dbEx)            {                GetException(dbEx);            }        }         /// <summary>        /// Update entity        /// </summary>        /// <param name="entity">Entity</param>        public virtual void Update(T entity)        {            try            {                if (entity == null)                    throw new ArgumentNullException("entity");                _context.SaveChanges();            }            catch (DbEntityValidationException dbEx)            {                GetException(dbEx);            }        }         /// <summary>        /// Delete entity        /// </summary>        /// <param name="entity">Entity</param>        public virtual void Delete(T entity)        {            try            {                if (entity == null)                    throw new ArgumentNullException("entity");                 Entities.Remove(entity);                 _context.SaveChanges();            }            catch (DbEntityValidationException dbEx)            {                GetException(dbEx);            }        }         /// <summary>        /// Delete entities        /// </summary>        /// <param name="entities">Entities</param>        public virtual void Delete(IEnumerable<T> entities)        {            try            {                if (entities == null)                    throw new ArgumentNullException("entities");                 foreach (var entity in entities)                    Entities.Remove(entity);                _context.SaveChanges();            }            catch (DbEntityValidationException dbEx)            {                GetException(dbEx);            }        }         private static void GetException(DbEntityValidationException dbEx)        {            var msg = string.Empty;             foreach (var validationErrors in dbEx.EntityValidationErrors)                foreach (var validationError in validationErrors.ValidationErrors)                    msg += Environment.NewLine +                           string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);             var fail = new Exception(msg, dbEx);            //Debug.WriteLine(fail.Message, fail);            throw fail;        }    }  View Code

而其中的IDbContext是自定义的数据接口

   public interface IDbContext     {         IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;          int SaveChanges();          /// <summary>         /// 执行存储过程,并返回对象列表         /// </summary>         /// <typeparam name="TEntity">The type of the T entity.</typeparam>         /// <param name="commandText">The command text.</param>         /// <param name="parameters">The parameters.</param>         /// <returns>IList{``0}.</returns>         IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters)             where TEntity : BaseEntity, new();         /// <summary>         /// 查询Sql语句         /// </summary>         /// <typeparam name="TElement"></typeparam>         /// <param name="sql"></param>         /// <param name="parameters"></param>         /// <returns></returns>         IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters);          /// <summary>         /// 执行sql 是否启用事务         /// </summary>         /// <param name="sql"></param>         /// <param name="doNotEnsureTransaction"></param>         /// <param name="timeout"></param>         /// <param name="parameters"></param>         /// <returns></returns>         int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null,             params object[] parameters);     }  View Code

然后注入:

   kernel.Bind<IDbContext>().To<PortalDb>().InSingletonScope(); 

对于和模型相关的Service内部都是注入的IRepository<T>,比如UserService。

       private readonly IRepository<User> _useRepository;        private readonly IRepository<UserRole> _userRoleRepository;        private readonly  ICacheManager _cacheManager ;         public UserService(IRepository<User> useRepository,IRepository<UserRole> userRoleRepository,ICacheManager cacheManager)        {            _useRepository = useRepository;            _userRoleRepository = userRoleRepository;            _cacheManager = cacheManager;        } 

这样相互之间就比较干净。只依赖接口。

而数据模型都会继承BaseEntity这个对象。

  public class User : BaseEntity     {   //...    } 

数据映射相关的部分在Mapping中,比如UserMap

  public class UserMap : PortalEntityTypeConfiguration<Domain.User.User>     {         public UserMap()         {             ToTable("Users");             HasKey(n => n.Id);             Property(n => n.Username).HasMaxLength(100);             Property(n => n.Email).HasMaxLength(500);             Ignore(n => n.PasswordFormat);             HasMany(c => c.UserRoles).WithMany().Map(m => m.ToTable("User_UserRole_Mapping"));         }     } 

在PortalDb的OnModelCreating方法中加入。这样就会影响到数据库的设计。

     protected override void OnModelCreating(DbModelBuilder modelBuilder)         {                   modelBuilder.Configurations.Add(new UserMap());         } 

Nop的做法高级一些。反射找出所有的PortalEntityTypeConfiguration。一次性加入。这里大家可以自己去试

   var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()             .Where(type => !String.IsNullOrEmpty(type.Namespace))             .Where(type => type.BaseType != null && type.BaseType.IsGenericType &&                 type.BaseType.GetGenericTypeDefinition() == typeof(PortalEntityTypeConfiguration<>));             foreach (var type in typesToRegister)             {                 dynamic configurationInstance = Activator.CreateInstance(type);                 modelBuilder.Configurations.Add(configurationInstance);             } 

权限管理:

默认设定了三种角色,Administrators,Admins,Employeer,他们分别配置了不同的权限,权限指定是在 StandardPermissionProvider 这个类中完成的,表示一个角色拥有哪些权限,Administrators拥有所有权限

 new DefaultPermissionRecord       {         UserRoleSystemName   = SystemUserRoleNames.Admin,         PermissionRecords = new []          {             AccessAdminPanel,             SearchOrder,             ManageUsers,           }        }, 

而权限验证的时候,在响应的Action上面加上AdminAuthorize和权限名称即可。

  [AdminAuthorize("ManageUsers")]    public ActionResult Index()     {     } 

而在其内部是通过PermissionService来验证的:

  public virtual bool HasAdminAccess(AuthorizationContext filterContext)         {             bool result = PermissionService.Authorize(Permission);             return result;         } 

后台只有给用户设置角色的页面,我没做新增角色并分配权限的页面。不过这个实现起来也很简单了。如果你是更换数据库,这个时候设计到权限的初始化。调用下面的方法即可:

 _permissionService.InstallPermissions(new StandardPermissionProvider()); 

当然前提是你先注入这个_permissionService。更多的代码细节大家可以去看源码。安装完成之后会有以下默认权限

Portal.MVC —— nopcommerce的简化版

以上是关于工程的大体说明,欢迎大家拍砖。下面下载地址。数据文件需要附加。

1:github地址: https://github.com/stoneniqiu/Portal.MVC

2:百度云:链接:http://pan.baidu.com/s/1o65vgyY 密码:f4wo

关于分享

首先还是希望这个工程对大家有帮助,分享的过程是对过去知识一个梳理,存档入库,这种感觉就像是盛了一杯水倒掉了,进而我需要找新的水装满这个水杯;最后的最后还是宣传一下我们的读书群。我们加了很多技术群,但终归寂寥。但这个群是 会持续分享读书心得和电子书 的。人生路长,你需要一个持续的精神粮食来源。下面是我们第四期书山有路的投票结果,《数学之美》现在正在进行。 群号:452450927

Portal.MVC —— nopcommerce的简化版

正文到此结束
Loading...