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