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并更新最后访问时间。

下面介绍下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; }
}

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

而其中的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);
}

然后注入:

  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();
Property(n => n.Email).HasMaxLength();
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
 

Portal.MVC —— nopcommerce的简化版的更多相关文章

  1. Portal.MVC

    Portal.MVC Portal.MVC 简介 项目是基于MVC4+EF,带有角色,权限,用户中心及账户相关(登录,注册,修改密码,找回密码等)等基本功能.参考的开源项目nopcommerce,这是 ...

  2. Asp.Net MVC 扩展 Html.ImageFor 方法详解

    背景: 在Asp.net MVC中定义模型的时候,DataType有DataType.ImageUrl这个类型,但htmlhelper却无法输出一个img,当用脚手架自动生成一些form或表格的时候, ...

  3. MVC 扩展 Html.ImageFor

    Asp.Net MVC 扩展 Html.ImageFor 方法详解 背景: 在Asp.net MVC中定义模型的时候,DataType有DataType.ImageUrl这个类型,但htmlhelpe ...

  4. ASP.NET MVC 手机短信验证

    本文来自于stoneniqiu的文章,原文地址 http://www.cnblogs.com/stoneniqiu/p/6234002.html 1.注册一个应用 得到AppKey 和 App Sec ...

  5. 微信硬件H5面板开发(二) ---- 实现一个灯的控制

    在第一节中讲解了openApi的调用,这一篇讲一下如何实现一个灯的控制.就用微信提供的lamp例子来做,将代码扒下来(实在是没办法,没有示例),整合到自己的项目中.lamp源码:http://file ...

  6. Fluent Validation

    .NET业务实体类验证组件Fluent Validation   认识Fluent Vaidation. 看到NopCommerce项目中用到这个组建是如此的简单,将数据验证从业务实体类中分离出来,真 ...

  7. spring+mybatis 配置双数据源

    配置好后,发现网上已经做好的了, 不过,跟我的稍有不同, 我这里再拿出来现个丑: properties 文件自不必说,关键是这里的xml: <?xml version="1.0&quo ...

  8. 【微信支付】分享一个失败的案例 跨域405(Method Not Allowed)问题 关于IM的一些思考与实践 基于WebSocketSharp 的IM 简单实现 【css3】旋转倒计时 【Html5】-- 塔台管制 H5情景意识 --飞机 谈谈转行

    [微信支付]分享一个失败的案例 2018-06-04 08:24 by stoneniqiu, 2744 阅读, 29 评论, 收藏, 编辑 这个项目是去年做的,开始客户还在推广,几个月后发现服务器已 ...

  9. [转][MVC] 剖析 NopCommerce 的 Theme 机制

    本文转自:http://www.cnblogs.com/coolite/archive/2012/12/28/NopTheme.html?utm_source=tuicool&utm_medi ...

随机推荐

  1. CSS3动画效果结合JS的轮播

    <style> *{;} #big{ width: 100%; height: 280px; } .carousel-wrapper{ width: 500px; height: 280p ...

  2. 解决Nginx不支持pathinfo的问题

    server { listen 80; server_name www.zq27.cc zq27.cc; root /data/wwwroot/www.zq27.cc/; access_log off ...

  3. HTML5之语义标签

    在HTML5标准中,新加了几个用于增添页面语义的标签,这些标签有:article.section.nav和aside等.与别的大多数标签不 同,浏览器在解释渲染这些标签的时候仅仅把它作为普通的div块 ...

  4. 《第一本docker书》- 第一章笔记

    环境: Ubuntu 14.04.2 LTS (GNU/Linux 3.16.0-30-generic i686) 第一章: 1 Docker客户端和服务器 2 Docker镜像 添加一个文件,执行一 ...

  5. jquery制作弹出层带遮罩效果,点击阴影部分层消失

    jquery制作弹出层带遮罩效果,点击阴影部分层消失. 整体还是比较简单的. HTML代码很简单 <a href="#" class="big-link" ...

  6. c# udp局域网通信

    udp224.0.0.1 子网上的所有系统224.0.0.2 子网上的所有路由器224.0.0.12 dhcp服务器224.0.1.1 ntp224.0.1.24 wins服务器 http://www ...

  7. NSURLSession的基本使用

    一.简单说明 在iOS9.0之后,以前使用的NSURLConnection过期,苹果推荐使用NSURLSession来替换NSURLConnection完成网路请求相关操作. NSURLSession ...

  8. hdoj 1596 find the safest rode

    Problem Description XX星球有很多城市,每个城市之间有一条或多条飞行通道,但是并不是所有的路都是很安全的,每一条路有一个安全系数s,s是在 0 和 1 间的实数(包括0,1),一条 ...

  9. Android视图状态及重绘流程分析,带你一步步深入了解View(三)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17045157 在前面一篇文章中,我带着大家一起从源码的层面上分析了视图的绘制流程, ...

  10. 无法卸载jdk的解决方法

    装了java之后非常纠结的就是无法卸载,总不能因为卸载一个jdk去重装系统,但是看着它残存在那又非常不爽, 因为卸载会牵扯注册表等琐碎的东西,,,后来在官网发现神器一枚,此神器就是java卸载工具. ...