Portal.MVC -- a simplified version of nopcommerce

Keywords: SQL Database github Excel

Introduction to Portal.MVC
The project is based on MVC4+EF, with the basic functions of role, authority, user center and account (login, registration, password modification, password retrieval, etc.). Open source projects for reference nopcommerce This is an MVC project of e-commerce architecture. I simplified it. Before that, it was mainly convenient for me to build some small websites. Includes front and back office.
Interface browsing
1. home page. This is a temporary Logo and page made the night before yesterday. It's not a real case. You can modify it according to the actual project.
2. login box
2. registration frame
3. To retrieve the mailbox password, you need to configure the mailbox server.
4. User center
4. Background management, used Matrix Admin Template. This is a good template, which is derived from Bootstrap.
 
User management. Here, NPOI is used for excel export.
 
I don't know if I mentioned the interest of garden friends. Let me talk about the code part.
Function introduction
I didn't divide it into too many levels, which is more intuitive. The core part is There are four parts in the Niqiu.Core class library
 
  • Domain: including main domain models, such as User,UserRole,PermissionRecord, etc
  • Helpers: contains some help classes, such as xml and email
  • Mapping: data mapping
  • Services: interface and implementation of service part

The important part of the website includes reusable Attributes,AccountController, etc., such as UserLastActivityIpAttribute, which will record the user Ip and update the last visit time.

Here are two parts that I think are important

Ninject dependency injection:

What is used in Nop Autofac And built a powerful EnginContext to manage all dependency injection items. In this project, I removed this part and replaced it with Ninject To complete the IOC work. It doesn't involve the advantages and disadvantages of these two components, and I think the former is more powerful. Ninject is registered in the NinjectWebCommon class. It is under the app ﹣ start folder. As follows:

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

For a more detailed introduction of Ninject, please see my blog: The use of Ninject in MVC5 . Where constructors cannot be used, property injection can be used.

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

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

Some services need HttpContext. This object has no interface and cannot be registered, but it can be obtained through HttpContextWrapper.

public HttpContextBase HttpContext
        {
            get { return new HttpContextWrapper(System.Web.HttpContext.Current); }
        }
HttpContextBase is an abstract class, and HttpContextWrapper is its derived member. Both were. Net3.5 later.

For more explanation, you can see Lao Zhao's blog: Why HttpContextBase instead of IHttpContext

Domain design

In this part, there are many discussions on the Internet. Nop adopts a single warehouse interface irepository < T > and then implements different services. Define IDbContext and inject database object.

 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

Implement this interface with 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

And the IDbContext is a custom data interface

public interface IDbContext
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;

        int SaveChanges();

        /// <summary>
        /// Execute the stored procedure and return to the object list
        /// </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>
        /// query Sql Sentence
        /// </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>
        /// implement sql Enable transaction or not
        /// </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

Then inject:

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

For services related to the model, they are all injected irepository < T >, such as 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;
       }

In this way, they are relatively clean. Interface only.

The data model will inherit the BaseEntity object.

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

The relevant parts of data Mapping are in Mapping, such as 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"));
        }
    }

Add in the OnModelCreating method of PortalDb. This will affect the design of the database.

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

Nop's approach is more advanced. Reflection finds all portaltentitytypeconfigurations. One time. Here you can try it on your own

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);
            }

Rights management:

By default, three roles are set: administrators, Admins, and employee. They are respectively configured with different permissions. Permissions are specified in the StandardPermissionProvider class, indicating which permissions a role has. Administrators have all permissions

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

During permission verification, add AdminAuthorize and permission name to the response Action.

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

Inside, it is verified by PermissionService:

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

In the background, there is only a page for setting roles for users. I have not made a page for adding roles and assigning permissions. But it's also very easy to implement. If you are changing the database, you should initialize the permissions at this time. Call the following method:

_permissionService.InstallPermissions(new StandardPermissionProvider());

Of course, the premise is that you first inject the "permissionService". You can go to the source code for more details. After the installation is complete, you will have the following default permissions

The above is a general description of the project. Welcome to make bricks. Download address below. Data files need to be attached.

1: github address: https://github.com/stoneniqiu/Portal.MVC 

2: Baidu cloud: https://pan.baidu.com/s/1juc5mo20stw0i5ujyanbw

The database is Sql2008, which can not be attached and can also be generated automatically. Remember to add users to yourself. Default user name: stoneniqiu password admin

About sharing
First of all, I hope this project will help you. The process of sharing is to sort out the past knowledge, archive it and put it into storage. This feeling is like pouring out a glass of water, and then I need to find new water to fill the glass. Finally, I want to publicize our reading group. We added a lot of technology groups, but it was lonely. But this group is Will continue to share reading experience and e-book. Life is a long journey. You need a sustainable source of spiritual food. The following is the voting result of our fourth book, the beauty of mathematics, which is now in progress. Group No.: 452450927
 

Posted by linda_v on Wed, 08 Apr 2020 02:03:04 -0700