原文链接:http://www.codeproject.com/Articles/1095323/Generic-Repository-Pattern-MVC

良好的架构师任何项目的核心,开发人员一直在寻找一个牛X的架构,它能减少重复代码,分离数据访问与业务逻辑。因此,我们需要在MVC中使用EF创建一个泛型仓储类。如果不了解EF,去这里学习。在开始之前,我们需要了解什么是仓储模式,为什么使用仓储模式。

仓储模式和工作单元

简言之,仓储模式意味着,数据访问层与业务逻辑层之间的抽象,这是非常有利于单元测试或TDD。通过使用仓储模式,你的系统会更加松散耦合。

在开发过程中,我们通常为每个仓储或实体创建一个接口。例如,我们为Student这个实体创建一个接口约束(IStudentInterface),定义所有的CRUD操作,同时创建另一个类(StudentRepository)实现接口中所定义的所有方法。当我们在控制器中实例化仓储类的时候,我们将使用实现了对应接口的类的引用。当控制器在运行的时候,它将调用在EF基础上工作的仓储类。

当对控制器进行单元测试的时候,我们可以操作仓储类的具体数据实现,例如内存数据集。这样我们可以使用伪数据进行单元测试。

如果你想了解详细的实现,可以参照如下链接:

getting started with ef 5 using mvc 4 / implementing the repository and unit of work patterns in an asp.net mvc application

不利因素

每次我们都需要为实体创建仓储类,导致代码冗余

代码实现

现在我们仅仅需要一个数据访问类,介绍一些实体和执行必要的操作,例如CRUD.在学习了很多文章、理论和示例代码后,我获得了一个很好的通用的仓储模式的实现。

我的代码在很大程度上基于Huy Nguyen的博客。请参阅以下链接

entity-framework-4-poco-repository-and-specification-pattern

entity-framework-poco-repository-and-specification-pattern-upgraded-to-ef-5

我修改了很多实现代码同时添加了一些在项目中常用的代码实现。现在我能使用这个类库在任何项目。下面是文件结构:

Mayur.DAL – 通用仓储和公共方法类库

  • Core – 文件夹

    • GlobalCommonHelper.cs – 一个为每个项目提供大部分公共方法的抽象类
  • Repository – 文件夹

    • IRepository.cs – 通用仓储接口
    • Repository.cs – 通用仓储实现类,继承与仓储接口
    • IUnitOfWork.cs – 工作单元接口.
    • UnitOfWork.cs – 实现了EF的SaveChanges()方法。工作单元类保证我们在对数据库执行事务性的插入、更新、删除操作时,直到我们执行Savechanges()方法以后EF才会提交所做的修改。

Mayur.Web – MVC Web项目

  • Controller – 文件夹

    • HomeController.cs – 包含CRUD动作的控制器
  • Core – 文件夹

    • CommonHelper.cs – 继承于 Mayur.DAL.Core.GlobalCommonHelper.cs which 包含MVC项目中相关的公共方法。
  • Model – 文件夹

    • Student.cs – 实体类
  • Views – Folder

    • Index.chtml – 不用说了吧都
    • Create.chtml – Create new student html
    • Edit.cshtml – Update student info html
    • Delete.cshtml – Delete student info html

让我们简单了解下DAL中每个文件的作用:

Repository 文件夹: 在这个文件夹,包含所有的数据访问逻辑。有4个文件, 2个接口文件,两个接口的实现类

  1. IRepository 接口

     public interface IRepository : IDisposable
    {
    /// <summary>
    /// 获取工作单元
    /// </summary>
    /// <value>The unit of work.</value>
    IUnitOfWork UnitOfWork { get; } /// <summary>
    /// Gets entity by key.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="keyValue">The key value.</param>
    /// <returns></returns>
    TEntity GetByKey<TEntity>(object keyValue) where TEntity : class; /// <summary>
    /// Gets the query.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <returns></returns>
    IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class; /// <summary>
    /// Gets the query.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="predicate">The predicate.</param>
    /// <returns></returns>
    IQueryable<TEntity> GetQuery<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class; /// <summary>
    /// Gets all.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <returns></returns>
    IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class; /// <summary>
    /// Gets the specified order by.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <typeparam name="TOrderBy">The type of the order by.</typeparam>
    /// <param name="orderBy">The order by.</param>
    /// <param name="pageIndex">Index of the page.</param>
    /// <param name="pageSize">Size of the page.</param>
    /// <param name="sortOrder">The sort order.</param>
    /// <returns></returns>
    IEnumerable<TEntity> Get<TEntity, TOrderBy>(Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex,int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class; /// <summary>
    /// Gets the specified criteria.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <typeparam name="TOrderBy">The type of the order by.</typeparam>
    /// <param name="criteria">The criteria.</param>
    /// <param name="orderBy">The order by.</param>
    /// <param name="pageIndex">Index of the page.</param>
    /// <param name="pageSize">Size of the page.</param>
    /// <param name="sortOrder">The sort order.</param>
    /// <returns></returns>
    IEnumerable<TEntity> Get<TEntity, TOrderBy>(Expression<Func<TEntity, bool>> criteria,
    Expression<Func<TEntity,
    TOrderBy>> orderBy,
    int pageIndex,
    int pageSize,
    SortOrder sortOrder = SortOrder.Ascending) where TEntity : class; /// <summary>
    /// Gets one entity based on matching criteria
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="criteria">The criteria.</param>
    /// <returns></returns>
    TEntity Single<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class; /// <summary>
    /// Firsts the specified predicate.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="predicate">The predicate.</param>
    /// <returns></returns>
    TEntity First<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class; /// <summary>
    /// Finds entities based on provided criteria.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="criteria">The criteria.</param>
    /// <returns></returns>
    IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class; /// <summary>
    /// Finds one entity based on provided criteria.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="criteria">The criteria.</param>
    /// <returns></returns>
    TEntity FindOne<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class; /// <summary>
    /// Counts the specified entities.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <returns></returns>
    int Count<TEntity>() where TEntity : class; /// <summary>
    /// Counts entities with the specified criteria.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="criteria">The criteria.</param>
    /// <returns></returns>
    int Count<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class; /// <summary>
    /// Adds the specified entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="entity">The entity.</param>
    void Add<TEntity>(TEntity entity) where TEntity : class; /// <summary>
    /// Attaches the specified entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="entity">The entity.</param>
    void Attach<TEntity>(TEntity entity) where TEntity : class; /// <summary>
    /// Updates changes of the existing entity.
    /// The caller must later call SaveChanges()
    /// on the repository explicitly to save the entity to database
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="entity">The entity.</param>
    void Update<TEntity>(TEntity entity) where TEntity : class; /// <summary>
    /// Deletes the specified entity.
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="entity">The entity.</param>
    void Delete<TEntity>(TEntity entity) where TEntity : class; /// <summary>
    /// Deletes one or many entities matching the specified criteria
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="criteria">The criteria.</param>
    void Delete<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class; /// <summary>
    /// Deletes entities which satisfy specificatiion
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <param name="criteria">The criteria.</param>
    //void Delete<TEntity>
    //(ISpecification<TEntity> criteria) where TEntity : class;
    }
  2. Repository 类

       1:     /// <summary>
       2:     ///     Generic repository Class
       3:     /// </summary>
       4:     public partial class Repository : IRepository, IDisposable
       5:     {
       6:         //Private Variables
       7:         private bool bDisposed;
       8:         private DbContext context;
       9:         private IUnitOfWork unitOfWork;
      10:   
      11:         #region Contructor Logic
      12:   
      13:         /// <summary>
      14:         /// Initializes a new instance of the
      15:         /// <see cref="Repository<TEntity>"/> class.
      16:         /// </summary>
      17:         public Repository()
      18:         {
      19:   
      20:         }
      21:   
      22:         /// <summary>
      23:         ///     Initializes a new instance of the
      24:         /// <see cref="Repository<TEntity>" /> class.
      25:         /// </summary>
      26:         /// <param name="context">The context.</param>
      27:         public Repository(DbContext contextObj)
      28:         {
      29:             if (contextObj == null)
      30:                 throw new ArgumentNullException("context");
      31:             this.context = contextObj;
      32:         }
      33:   
      34:         public Repository(ObjectContext contextObj)
      35:         {
      36:             if (this.context == null)
      37:                 throw new ArgumentNullException("context");
      38:             context = new DbContext(contextObj, true);
      39:         }
      40:   
      41:         public void Dispose()
      42:         {
      43:             Close();
      44:         }
      45:   
      46:         #endregion
      47:   
      48:         #region Properties
      49:   
      50:         //DbContext Property
      51:         protected DbContext DbContext
      52:         {
      53:             get
      54:             {
      55:                 if (context == null)
      56:                     throw new ArgumentNullException("context");
      57:   
      58:                 return context;
      59:             }
      60:         }
      61:   
      62:         //Unit of Work Property
      63:         public IUnitOfWork UnitOfWork
      64:         {
      65:             get
      66:             {
      67:                 if (unitOfWork == null)
      68:                 {
      69:                     unitOfWork = new UnitOfWork(DbContext);
      70:                 }
      71:                 return unitOfWork;
      72:             }
      73:         }
      74:   
      75:         #endregion
      76:   
      77:         #region Data Display Methods
      78:   
      79:         //Helper Method tp create Query [IQuerable]
      80:   
      81:         public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
      82:         {
      83:             EntityKey key = GetEntityKey<TEntity>(keyValue);
      84:   
      85:             object originalItem;
      86:             if (((IObjectContextAdapter)DbContext).
      87:             ObjectContext.TryGetObjectByKey(key, out originalItem))
      88:             {
      89:                 return (TEntity)originalItem;
      90:             }
      91:   
      92:             return default(TEntity);
      93:         }
      94:   
      95:         public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
      96:         {
      97:             string entityName = GetEntityName<TEntity>();
      98:             return ((IObjectContextAdapter)DbContext).
      99:             ObjectContext.CreateQuery<TEntity>(entityName);
     100:         }
     101:   
     102:         public IQueryable<TEntity> GetQuery<TEntity>
     103:         (Expression<Func<TEntity, bool>> predicate) where TEntity : class
     104:         {
     105:             return GetQuery<TEntity>().Where(predicate);
     106:         }
     107:   
     108:   
     109:         //All Readonly Display or fetch data methods.
     110:         public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
     111:         {
     112:             return GetQuery<TEntity>().AsEnumerable();
     113:         }
     114:   
     115:         public IEnumerable<TEntity> Get<TEntity, TOrderBy>
     116:         (Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex,
     117:             int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
     118:         {
     119:             if (sortOrder == SortOrder.Ascending)
     120:             {
     121:                 return GetQuery<TEntity>()
     122:                     .OrderBy(orderBy)
     123:                     .Skip((pageIndex - 1) * pageSize)
     124:                     .Take(pageSize)
     125:                     .AsEnumerable();
     126:             }
     127:             return
     128:                 GetQuery<TEntity>()
     129:                     .OrderByDescending(orderBy)
     130:                     .Skip((pageIndex - 1) * pageSize)
     131:                     .Take(pageSize)
     132:                     .AsEnumerable();
     133:         }
     134:   
     135:         public IEnumerable<TEntity> Get<TEntity,
     136:         TOrderBy>(Expression<Func<TEntity, bool>> criteria,
     137:             Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex, int pageSize,
     138:             SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
     139:         {
     140:             if (sortOrder == SortOrder.Ascending)
     141:             {
     142:                 return GetQuery(criteria).
     143:                     OrderBy(orderBy).
     144:                     Skip((pageIndex - 1) * pageSize).
     145:                     Take(pageSize)
     146:                     .AsEnumerable();
     147:             }
     148:             return
     149:                 GetQuery(criteria)
     150:                     .OrderByDescending(orderBy)
     151:                     .Skip((pageIndex - 1) * pageSize)
     152:                     .Take(pageSize)
     153:                     .AsEnumerable();
     154:         }
     155:   
     156:         public TEntity Single<TEntity>
     157:         (Expression<Func<TEntity, bool>> criteria) where TEntity : class
     158:         {
     159:             return GetQuery<TEntity>().Single<TEntity>(criteria);
     160:         }
     161:   
     162:         public TEntity First<TEntity>
     163:         (Expression<Func<TEntity, bool>> predicate) where TEntity : class
     164:         {
     165:             return GetQuery<TEntity>().First(predicate);
     166:         }
     167:   
     168:         public IEnumerable<TEntity> Find<TEntity>
     169:         (Expression<Func<TEntity, bool>> criteria) where TEntity : class
     170:         {
     171:             return GetQuery<TEntity>().Where(criteria);
     172:         }
     173:   
     174:         public TEntity FindOne<TEntity>
     175:         (Expression<Func<TEntity, bool>> criteria) where TEntity : class
     176:         {
     177:             return GetQuery<TEntity>().Where(criteria).FirstOrDefault();
     178:         }
     179:   
     180:         public int Count<TEntity>() where TEntity : class
     181:         {
     182:             return GetQuery<TEntity>().Count();
     183:         }
     184:   
     185:         public int Count<TEntity>
     186:         (Expression<Func<TEntity, bool>> criteria) where TEntity : class
     187:         {
     188:             return GetQuery<TEntity>().Count(criteria);
     189:         }
     190:   
     191:         #endregion
     192:   
     193:         #region Data Transactional Methods
     194:   
     195:         public void Add<TEntity>(TEntity entity) where TEntity : class
     196:         {
     197:             if (entity == null)
     198:             {
     199:                 throw new ArgumentNullException("entity");
     200:             }
     201:             DbContext.Set<TEntity>().Add(entity);
     202:         }
     203:   
     204:         public void Attach<TEntity>(TEntity entity) where TEntity : class
     205:         {
     206:             if (entity == null)
     207:             {
     208:                 throw new ArgumentNullException("entity");
     209:             }
     210:   
     211:             DbContext.Set<TEntity>().Attach(entity);
     212:         }
     213:   
     214:         public void Update<TEntity>(TEntity entity) where TEntity : class
     215:         {
     216:             string fqen = GetEntityName<TEntity>();
     217:   
     218:             object originalItem;
     219:             EntityKey key =
     220:             ((IObjectContextAdapter)DbContext).ObjectContext.CreateEntityKey(fqen, entity);
     221:             if (((IObjectContextAdapter)DbContext).ObjectContext.TryGetObjectByKey
     222:             (key, out originalItem))
     223:             {
     224:                 ((IObjectContextAdapter)DbContext).ObjectContext.ApplyCurrentValues
     225:                 (key.EntitySetName, entity);
     226:             }
     227:         }
     228:   
     229:         public void Delete<TEntity>(TEntity entity) where TEntity : class
     230:         {
     231:             if (entity == null)
     232:             {
     233:                 throw new ArgumentNullException("entity");
     234:             }
     235:             DbContext.Set<TEntity>().Remove(entity);
     236:         }
     237:   
     238:         public void Delete<TEntity>(Expression<Func<TEntity,
     239:         bool>> criteria) where TEntity : class
     240:         {
     241:             IEnumerable<TEntity> records = Find(criteria);
     242:   
     243:             foreach (TEntity record in records)
     244:             {
     245:                 Delete(record);
     246:             }
     247:         }
     248:   
     249:         #endregion
     250:   
     251:         #region Internal Processing Private Methods
     252:   
     253:         private EntityKey GetEntityKey<TEntity>(object keyValue) where TEntity : class
     254:         {
     255:             string entitySetName = GetEntityName<TEntity>();
     256:             ObjectSet<TEntity> objectSet =
     257:             ((IObjectContextAdapter)DbContext).ObjectContext.CreateObjectSet<TEntity>();
     258:             string keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
     259:             var entityKey = new EntityKey
     260:             (entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
     261:             return entityKey;
     262:         }
     263:   
     264:         private string GetEntityName<TEntity>() where TEntity : class
     265:         {
     266:             // Thanks to Kamyar Paykhan -
     267:             // http://huyrua.wordpress.com/2011/04/13/
     268:             // entity-framework-4-poco-repository-and-specification-pattern-upgraded-to-ef-4-1/
     269:             // #comment-688
     270:             string entitySetName = ((IObjectContextAdapter)DbContext).ObjectContext
     271:                 .MetadataWorkspace
     272:                 .GetEntityContainer(((IObjectContextAdapter)DbContext).
     273:                     ObjectContext.DefaultContainerName,
     274:                     DataSpace.CSpace)
     275:                 .BaseEntitySets.Where(bes => bes.ElementType.Name == typeof(TEntity).Name).First().Name;
     276:             return string.Format("{0}.{1}",
     277:             ((IObjectContextAdapter)DbContext).ObjectContext.DefaultContainerName,
     278:                 entitySetName);
     279:         }
     280:   
     281:         private string RemoveAccent(string txt)
     282:         {
     283:             byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(txt);
     284:             return System.Text.Encoding.ASCII.GetString(bytes);
     285:         }
     286:   
     287:         private bool IsValidTag(string tag, string tags)
     288:         {
     289:             string[] allowedTags = tags.Split(',');
     290:             if (tag.IndexOf("javascript") >= 0) return false;
     291:             if (tag.IndexOf("vbscript") >= 0) return false;
     292:             if (tag.IndexOf("onclick") >= 0) return false;
     293:   
     294:             var endchars = new char[] { ' ', '>', '/', '\t' };
     295:   
     296:             int pos = tag.IndexOfAny(endchars, 1);
     297:             if (pos > 0) tag = tag.Substring(0, pos);
     298:             if (tag[0] == '/') tag = tag.Substring(1);
     299:   
     300:             foreach (string aTag in allowedTags)
     301:             {
     302:                 if (tag == aTag) return true;
     303:             }
     304:   
     305:             return false;
     306:         }
     307:   
     308:         #endregion
     309:   
     310:         #region Disposing Methods
     311:   
     312:         protected void Dispose(bool bDisposing)
     313:         {
     314:             if (!bDisposed)
     315:             {
     316:                 if (bDisposing)
     317:                 {
     318:                     if (null != context)
     319:                     {
     320:                         context.Dispose();
     321:                     }
     322:                 }
     323:                 bDisposed = true;
     324:             }
     325:         }
     326:   
     327:         public void Close()
     328:         {
     329:             Dispose(true);
     330:             GC.SuppressFinalize(this);
     331:         }
     332:   
     333:         #endregion
     334:     }
  3. IUnitOfWork 接口

       1:      public interface IUnitOfWork : IDisposable
       2:      {
       3:          void SaveChanges();
       4:      }
  4. UnitOfWork 类

       1:      internal class UnitOfWork : IUnitOfWork
       2:      {
       3:          private readonly DbContext _dbContext;
       4:   
       5:          public UnitOfWork(DbContext context)
       6:          {
       7:              _dbContext = context;
       8:          }
       9:   
      10:          public void SaveChanges()
      11:          {
      12:              ((IObjectContextAdapter)_dbContext).ObjectContext.SaveChanges();
      13:          }
      14:   
      15:          #region Implementation of IDisposable
      16:   
      17:          private bool _disposed;
      18:   
      19:          /// <summary>
      20:          ///     Performs application-defined tasks associated with freeing, 
      21:          ///     releasing, or resetting unmanaged resources.
      22:          /// </summary>
      23:          public void Dispose()
      24:          {
      25:              Dispose(true);
      26:              GC.SuppressFinalize(this);
      27:          }
      28:   
      29:          /// <summary>
      30:          ///     Disposes off the managed and unmanaged resources used.
      31:          /// </summary>
      32:          /// <param name="disposing"></param>
      33:          private void Dispose(bool disposing)
      34:          {
      35:              if (!disposing)
      36:                  return;
      37:   
      38:              if (_disposed)
      39:                  return;
      40:   
      41:              _disposed = true;
      42:          }
      43:   
      44:          #endregion
      45:      }

在Mayur.DAL.Core文件夹中,还有一个抽象类,包含了一些项目中常用到的公共方法,如果你也有一些新的方法函数是我们在项目中需要的,请在评论中提出建议(原文这么说的,在我这评论我也不介意)。

  1. GlobalCommonHelper.cs 类

       1:   abstract public class GlobalCommonHelper
       2:      {
       3:          #region General Methods
       4:   
       5:          /// <summary>
       6:          /// Take any string and encrypt it using SHA1 then
       7:          /// return the encrypted data
       8:          /// </summary>
       9:          /// <param name="data">input text you will enterd to encrypt it</param>
      10:          /// <returns>return the encrypted text as hexadecimal string</returns>
      11:          public string GetSHA1HashData(string data)
      12:          {
      13:              //create new instance of md5
      14:              SHA1 sha1 = SHA1.Create();
      15:   
      16:              //convert the input text to array of bytes
      17:              byte[] hashData = sha1.ComputeHash(Encoding.Default.GetBytes(data));
      18:   
      19:              //create new instance of StringBuilder to save hashed data
      20:              StringBuilder returnValue = new StringBuilder();
      21:   
      22:              //loop for each byte and add it to StringBuilder
      23:              for (int i = 0; i < hashData.Length; i++)
      24:              {
      25:                  returnValue.Append(hashData[i].ToString());
      26:              }
      27:   
      28:              // return hexadecimal string
      29:              return returnValue.ToString();
      30:          }
      31:   
      32:          /// <summary>
      33:          /// Creates a slug url from string .
      34:          /// </summary>
      35:          /// <param name="phrase"></param>
      36:          /// <returns></returns>
      37:          public string GetSlugURLFromString(string phrase)
      38:          {
      39:              string str = RemoveAccent(phrase).ToLower();
      40:              // invalid chars          
      41:              str = Regex.Replace(str, @"[^a-z0-9\s-]", "");
      42:              // convert multiple spaces into one space  
      43:              str = Regex.Replace(str, @"\s+", " ").Trim();
      44:              // cut and trim
      45:              str = str.Substring(0, str.Length <= 45 ? str.Length : 45).Trim();
      46:              str = Regex.Replace(str, @"\s", "-"); // hyphens  
      47:              return str;
      48:          }
      49:   
      50:          /// <summary>
      51:          /// Delete file by specified path.
      52:          /// </summary>
      53:          /// <param name="path">path of file.</param>
      54:          public void DeleteTargetFile(string path)
      55:          {
      56:              if (File.Exists(path))
      57:              {
      58:                  File.SetAttributes(path, FileAttributes.Normal);
      59:                  File.Delete(path);
      60:              }
      61:          }
      62:   
      63:          /// <summary>
      64:          /// Sent email to target email address with attachment.
      65:          /// </summary>
      66:          /// <param name="toEmail">Email addresses of 
      67:          /// one or multiple receipients semi colon (;) separated values.</param>
      68:          /// <param name="subject">Email subject</param>
      69:          /// <param name="body">Email body</param>
      70:          /// <returns>True | False</returns>
      71:          public bool SendEmailToTarget(string toEmail, string subject, string body)
      72:          {
      73:   
      74:              bool success = false;
      75:              try
      76:              {
      77:                  SmtpClient SmtpServer = new SmtpClient();
      78:                  MailMessage mail = new MailMessage();
      79:   
      80:                  SmtpServer.Credentials = new NetworkCredential(
      81:                      Convert.ToString(ConfigurationManager.AppSettings["fromEmail"]),
      82:                      Convert.ToString(ConfigurationManager.AppSettings["fromPassword"]));
      83:   
      84:                  SmtpServer.Host = Convert.ToString
      85:                  (ConfigurationManager.AppSettings["hostName"]);
      86:                  SmtpServer.Port = Convert.ToInt32
      87:                  (ConfigurationManager.AppSettings["portNumber"]);
      88:   
      89:                  if (Convert.ToBoolean
      90:                  (ConfigurationManager.AppSettings["isEnableSSL"]) == true)
      91:                      SmtpServer.EnableSsl = true;
      92:   
      93:                  mail.From = new MailAddress(Convert.ToString
      94:                  (ConfigurationManager.AppSettings["senderName"]));
      95:   
      96:                  string[] multiEmails = toEmail.Split(';');
      97:                  foreach (string email in multiEmails)
      98:                  {
      99:                      mail.To.Add(email);
     100:                  }
     101:   
     102:                  mail.Subject = subject;
     103:                  mail.IsBodyHtml = true;
     104:                  mail.Body = body;
     105:                  SmtpServer.Send(mail);
     106:                  mail.Dispose();
     107:                  success = true;
     108:              }
     109:              catch (Exception)
     110:              {
     111:                  success = false;
     112:              }
     113:              return success;
     114:          }
     115:   
     116:          /// <summary>
     117:          /// Sent email to target email address with attachment.
     118:          /// </summary>
     119:          /// <param name="toEmail">Email addresses of 
     120:          /// one or multiple receipients semi colon (;) separated values.</param>
     121:          /// <param name="subject">Email subject</param>
     122:          /// <param name="body">Email body</param>
     123:          /// <param name="body">Email attachment file path</param>
     124:          /// <returns>True | False</returns>
     125:          public bool SendEmailToTarget(string toEmail, string subject, string body, string attachmentPath)
     126:          {
     127:   
     128:              bool success = false;
     129:              try
     130:              {
     131:                  SmtpClient SmtpServer = new SmtpClient();
     132:                  MailMessage mail = new MailMessage();
     133:   
     134:                  SmtpServer.Credentials = new NetworkCredential(
     135:                      Convert.ToString(ConfigurationManager.AppSettings["fromEmail"]),
     136:                      Convert.ToString(ConfigurationManager.AppSettings["fromPassword"]));
     137:   
     138:                  SmtpServer.Host = Convert.ToString
     139:                  (ConfigurationManager.AppSettings["hostName"]);
     140:                  SmtpServer.Port = Convert.ToInt32
     141:                  (ConfigurationManager.AppSettings["portNumber"]);
     142:   
     143:                  if (Convert.ToBoolean(ConfigurationManager.AppSettings["isEnableSSL"]) == true)
     144:                      SmtpServer.EnableSsl = true;
     145:   
     146:                  mail.From = new MailAddress(Convert.ToString
     147:                  (ConfigurationManager.AppSettings["senderName"]));
     148:   
     149:                  string[] multiEmails = toEmail.Split(';');
     150:                  foreach (string email in multiEmails)
     151:                  {
     152:                      mail.To.Add(email);
     153:                  }
     154:   
     155:                  Attachment attachment;
     156:                  attachment = new System.Net.Mail.Attachment(attachmentPath);
     157:                  mail.Attachments.Add(attachment);
     158:   
     159:                  mail.Subject = subject;
     160:                  mail.IsBodyHtml = true;
     161:                  mail.Body = body;
     162:                  SmtpServer.Send(mail);
     163:                  mail.Dispose();
     164:                  success = true;
     165:              }
     166:              catch (Exception)
     167:              {
     168:                  success = false;
     169:              }
     170:              return success;
     171:          }
     172:   
     173:          /// <summary>
     174:          /// Strips tags
     175:          /// </summary>
     176:          /// <param name="text">Text</param>
     177:          /// <returns>Formatted text</returns>
     178:          public string RemoveHtmlFromString(string text)
     179:          {
     180:              if (String.IsNullOrEmpty(text))
     181:              {
     182:                  return string.Empty;
     183:              }
     184:              else{
     185:                  
     186:   
     187:              text = Regex.Replace(text, @"(>)(\r|\n)*(<)", "><");
     188:              text = Regex.Replace(text, "(<[^>]*>)([^<]*)", "$2");
     189:              text = Regex.Replace(text, "(&#x?[0-9]{2,4};||&| |<|>|&euro;|&copy;|&reg;|&permil;|&Dagger;|&dagger;|&lsaquo;|&rsaquo;|&bdquo;|&rdquo;|&ldquo;|&sbquo;|&rsquo;|&lsquo;|&mdash;|&ndash;|&rlm;|&lrm;|&zwj;|&zwnj;|&thinsp;|&emsp;|&ensp;|&tilde;|&circ;|&Yuml;|&scaron;|&Scaron;)", "@");
     190:              return text;
     191:  }
     192:          }
     193:   
     194:          /// <summary>
     195:          /// Verifies that a string is in valid e-mail format
     196:          /// </summary>
     197:          /// <param name="email">Email to verify</param>
     198:          /// <returns>true if the string is a valid e-mail address and false if it's not</returns>
     199:          public bool IsValidEmail(string email)
     200:          {
     201:              if (String.IsNullOrEmpty(email))
     202:                  return false;
     203:   
     204:              email = email.Trim();
     205:              var result = Regex.IsMatch(email, "^(?:[\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`\\{\\|\\}\\~]+\\.)*[\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`\\{\\|\\}\\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-](?!\\.)){0,61}[a-zA-Z0-9]?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\\[(?:(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.){3}(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\]))$", RegexOptions.IgnoreCase);
     206:              return result;
     207:          }
     208:   
     209:          /// <summary>
     210:          /// Returns Allowed HTML only.
     211:          /// </summary>
     212:          /// <param name="text">Text</param>
     213:          /// <returns>Allowed HTML</returns>
     214:          public string EnsureOnlyAllowedHtml(string text)
     215:          {
     216:              if (String.IsNullOrEmpty(text))
     217:                  return string.Empty;
     218:   
     219:              const string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,blockquote,img,span,p,em," +
     220:                                          "strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite";
     221:   
     222:              var m = Regex.Matches(text, "<.*?>", RegexOptions.IgnoreCase);
     223:              for (int i = m.Count - 1; i >= 0; i--)
     224:              {
     225:                  string tag = text.Substring(m[i].Index + 1, m[i].Length - 1).Trim().ToLower();
     226:   
     227:                  if (!IsValidTag(tag, allowedTags))
     228:                  {
     229:                      text = text.Remove(m[i].Index, m[i].Length);
     230:                  }
     231:              }
     232:   
     233:              return text;
     234:          }
     235:   
     236:          #endregion
     237:   
     238:          #region Internal Processing Private Methods
     239:   
     240:          private string RemoveAccent(string txt)
     241:          {
     242:              byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(txt);
     243:              return System.Text.Encoding.ASCII.GetString(bytes);
     244:          }
     245:   
     246:          private bool IsValidTag(string tag, string tags)
     247:          {
     248:              string[] allowedTags = tags.Split(',');
     249:              if (tag.IndexOf("javascript") >= 0) return false;
     250:              if (tag.IndexOf("vbscript") >= 0) return false;
     251:              if (tag.IndexOf("onclick") >= 0) return false;
     252:   
     253:              var endchars = new char[] { ' ', '>', '/', '\t' };
     254:   
     255:              int pos = tag.IndexOfAny(endchars, 1);
     256:              if (pos > 0) tag = tag.Substring(0, pos);
     257:              if (tag[0] == '/') tag = tag.Substring(1);
     258:   
     259:              foreach (string aTag in allowedTags)
     260:              {
     261:                  if (tag == aTag) return true;
     262:              }
     263:   
     264:              return false;
     265:          }
     266:   
     267:          #endregion
     268:   
     269:      }

现在通用仓储已经具备了公共的方法,现在来看下我们怎么在控制器中使用它。假设我们有一个Student实体,包含studentID, name, rollNo 等列,下面是控制器中的代码

  1. 在继续之前,我们需要先完善数据上下文信息,以生成数据库和数据表 ,如下:

       1:      public class MyFirstDbContext:DbContext
       2:      {
       3:          public MyFirstDbContext()
       4:              : base("name=MyFirstDbContext")
       5:          {
       6:              Database.SetInitializer<MyFirstDbContext>(null);
       7:          }
       8:   
       9:          public virtual DbSet<Book> Books { get; set; }
      10:   
      11:          protected override void OnModelCreating(DbModelBuilder modelBuilder)
      12:          {
      13:   
      14:          }
      15:   
      16:      }
  2. 我在数据访问层创建了一个抽象的包含了每个项目中都要使用的公共方法的类。因为是一个抽象类,这意味着我们为它创建实例。所以我们需要创建一个新的类在Web项目中,这个类继承于GlobalCommonHelper类,命名为CommonHelper,我们可以在这个类中实现一些项目独有的公共方法。

       1:      public class CommonHelper : GlobalCommonHelper
       2:      {
       3:          public int PageSize = 25;
       4:          //Some common functions. Only Project-specific.     
       5:      }
  3. 现在我们可以看下如何在我们的控制器中使用仓储类,看控制器中的代码:

       1:  public class BooksController : Controller
       2:      {
       3:          private IRepository repository;
       4:          private CommonHelper helper;
       5:   
       6:          public BooksController()
       7:          {
       8:              repository=new Repository(new MyFirstDbContext());
       9:              helper=new CommonHelper();
      10:          }
      11:   
      12:          // GET: Books
      13:          public ActionResult Index()
      14:          {
      15:              var list = repository.GetAll<Book>();
      16:              return View(list);
      17:          }
      18:   
      19:          // GET: Books/Details/5
      20:          public ActionResult Details(int id=0)
      21:          {
      22:              if (id == 0)
      23:              {
      24:                  return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
      25:              }
      26:   
      27:              Book book = repository.FindOne<Book>(b => b.Id == id);
      28:              if (book == null)
      29:              {
      30:                  return HttpNotFound();
      31:              }
      32:              return View(book);
      33:          }
      34:   
      35:          // GET: Books/Create
      36:          public ActionResult Create()
      37:          {
      38:              return View();
      39:          }
      40:   
      41:          // POST: Books/Create
      42:          // 为了防止“过多发布”攻击,请启用要绑定到的特定属性,有关 
      43:          // 详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=317598。
      44:          [HttpPost]
      45:          [ValidateAntiForgeryToken]
      46:          public ActionResult Create([Bind(Include = "Id,Cover,BookName,Author,TranslatedName,Translator,Publisher,WordCount,Pages,ISBN,Price,SalePrice,PublicationDate,Introduction,AboutTheAuthors,Link")] Book book)
      47:          {
      48:              if (ModelState.IsValid)
      49:              {
      50:                  repository.Add(book);
      51:                  repository.UnitOfWork.SaveChanges();
      52:                  
      53:                  return RedirectToAction("Index");
      54:              }
      55:   
      56:              return View(book);
      57:          }
      58:   
      59:          // GET: Books/Edit/5
      60:          public ActionResult Edit(int id=0)
      61:          {
      62:              if (id == 0)
      63:              {
      64:                  return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
      65:              }
      66:              Book book = repository.FindOne<Book>(x => x.Id == id);
      67:              if (book == null)
      68:              {
      69:                  return HttpNotFound();
      70:              }
      71:              return View(book);
      72:          }
      73:   
      74:          // POST: Books/Edit/5
      75:          // 为了防止“过多发布”攻击,请启用要绑定到的特定属性,有关 
      76:          // 详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=317598。
      77:          [HttpPost]
      78:          [ValidateAntiForgeryToken]
      79:          public ActionResult Edit([Bind(Include = "Id,Cover,BookName,Author,TranslatedName,Translator,Publisher,WordCount,Pages,ISBN,Price,SalePrice,PublicationDate,Introduction,AboutTheAuthors,Link")] Book book)
      80:          {
      81:              if (ModelState.IsValid)
      82:              {
      83:                  repository.Update<Book>(book);
      84:                  repository.UnitOfWork.SaveChanges();
      85:                  //db.Entry(book).State = EntityState.Modified;
      86:                  //db.SaveChanges();
      87:                  return RedirectToAction("Index");
      88:              }
      89:              return View(book);
      90:          }
      91:   
      92:          // GET: Books/Delete/5
      93:          public ActionResult Delete(int id=0)
      94:          {
      95:              if (id == 0)
      96:              {
      97:                  return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
      98:              }
      99:              Book book = repository.FindOne<Book>(b => b.Id == id);
     100:              if (book == null)
     101:              {
     102:                  return HttpNotFound();
     103:              }
     104:              return View(book);
     105:          }
     106:   
     107:          // POST: Books/Delete/5
     108:          [HttpPost, ActionName("Delete")]
     109:          [ValidateAntiForgeryToken]
     110:          public ActionResult DeleteConfirmed(int id)
     111:          {
     112:              Book book = repository.FindOne<Book>(b => b.Id == id);
     113:              if (book == null)
     114:              {
     115:                  return HttpNotFound();
     116:              }
     117:              repository.Delete<Book>(book);
     118:              repository.UnitOfWork.SaveChanges();
     119:              //db.Books.Remove(book);
     120:              //db.SaveChanges();
     121:              return RedirectToAction("Index");
     122:          }
     123:        
     124:      }

我需要更多来自你的建议和改进,请提给我吧。(原作)

译注

正好在学习仓储模式和工作单元,想整理一个通用的仓储类,作者的做参考。

英语不好有些翻译很生硬,还有的直接表意,省掉了作者一些话(他们写文章都很认真,像教小学生一样敦敦教诲)

版本

•31/05/2015: Article published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

05-12 14:12