Portal.MVC —— nopcommerce的簡化版

Portal.MVC 簡介
項目是基於MVC4+EF,帶有角色,權限,用戶中心及帳戶相關(登陸,註冊,修改密碼,找回密碼等)等基本功能。參考的開源項目 nopcommerce,這是一個電商架構的MVC項目,我對其進行了簡化,以前主要是方便我本身搭建一些小的網站。包含前臺和後臺。
界面瀏覽
1.首頁。這是前天晚上臨時作出來的Logo和頁面。不是真實案例,你們能夠結合實際項目再修改。
2.登陸框
2.註冊框
3.郵箱密碼找回,這個地方要用還須要配置郵箱服務器。
4.用戶中心
 4.後臺管理,使用的 Matrix Admin 模板。這是一個很好用的模板,是衍生於Bootstrap。
 
 用戶管理,這裏excel導出用的是NPOI。
 
 不知道有沒有提起園友的興趣,下面我講一下代碼部分。
功能介紹
我沒有作分太多的層級,這樣比較直觀,核心的部分在 Niqiu.Core類庫裏面.分四個部分
 
  • Domain:包含主要的領域模型,好比User,UserRole,PermissionRecord等
  • Helpers:包含一些幫助類,好比xml,email
  • Mapping:數據映射
  • Services:服務部分的接口和實現

而網站部分重要的有一些能夠複用的Attributes,AccountController等,好比UserLastActivityIpAttribute,會記錄用戶的Ip並更新最後訪問時間。html

下面介紹下2個我認爲重要點的部分git

Ninject依賴注入:

Nop中使用的是Autofac,並構建了一個強大的EnginContext管理全部的依賴注入項,在這個項目中我拿掉了這一部分,換成Ninject來完成IOC的工做。並不涉及這兩個組件的優劣問題,並且我認爲前者的功能更要強悍一些。Ninject是在NinjectWebCommon類中註冊的.它在App_Start文件夾下。 以下:github

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

Ninject更詳細的介紹請看個人博客:Ninject在MVC5中的使用。在不能用構造函數的地方,能夠用屬性注入。sql

  [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>:app

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>實現這個接口ide

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。更多的代碼細節你們能夠去看源碼。安裝完成以後會有如下默認權限

 以上是關於工程的大致說明,歡迎你們拍磚。下面下載地址。數據文件須要附加。

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

 2:百度雲:https://pan.baidu.com/s/1juc5Mo20sTW0I5uJyaNBbw

 數據庫是Sql2008,附加不上,也能夠自動生成。記得給本身添加用戶。 默認用戶名:stoneniqiu 密碼 admin

關於分享
首先仍是但願這個工程對你們有幫助,分享的過程是對過去知識一個梳理,存檔入庫,這種感受就像是盛了一杯水倒掉了,進而我須要找新的水裝滿這個水杯;最後的最後仍是宣傳一下咱們的讀書羣。咱們加了不少技術羣,但終歸寂寥。但這個羣是 會持續分享讀書心得和電子書的。人生路長,你須要一個持續的精神糧食來源。下面是咱們第四期書山有路的投票結果,《數學之美》如今正在進行。 羣號:452450927
 
相關文章
相關標籤/搜索