【.NET架构】BIM软件架构02:Web管控平台后台架构
一、前言
?????? 之前一篇敘述的是Revit插件(桌面軟件)的軟件架構,本篇將開始敘述Web項目的架構方案。今年一月在老東家加入BIM平臺部門,為一些大型國家項目搭建BIM管控平臺,業主使用管控平臺可以實時了解各部門的施工狀態(包括進度、現場管理、產值等等),將這些信息與WebGL三維模型中的構件相互關聯就可以監控整個施工項目。
?????? 我們知道,一個Web項目如果使用MVC的方式的話常常使用到ApiController去請求數據,并根據返回的數據進行頁面的更新。由于平臺項目屬于大型項目,所以平臺架構師將ApiController/Action函數里的核心業務邏輯抽出,放在另一個解決方案中,即CoreService.sln。Web項目只要引用CoreService里的dlls,并以Unity注入的方式使用即可。為了不會讓大家看得太暈,本篇還是以CoreService.sln為主,如何調用的話會寫在下一篇中。
?
二、架構簡圖
可以看到,整個項目是比較清晰的:
1. DataBase.EFModel就是EntityFramework進行ORM放置edmx系列文件的地方。
2. Common用于放置一些基本的操作函數。
using Microsoft.Practices.Unity;using Microsoft.Practices.Unity.Configuration;
//容器注冊的一些方法
public class ServiceLocator : IServiceProvider{private readonly IUnityContainer mobjContainer = null;private static readonly ServiceLocator instance = new ServiceLocator();private ServiceLocator(){UnityConfigurationSection section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;mobjContainer = new UnityContainer();section.Configure(mobjContainer, "Default");}public static ServiceLocator Instance{get { return instance; }}public IUnityContainer GetContainer(){return mobjContainer;}public object GetService(Type serviceType){if (IsRegistered(serviceType)){return mobjContainer.Resolve(serviceType);}else{ServerLogger.Warn(string.Format("Service type {0} is not registered", serviceType.ToString()));return null;}}public object GetService(Type serviceType, string name){if (IsRegistered(serviceType, name)){return mobjContainer.Resolve(serviceType, name);}else{ServerLogger.Warn(string.Format("Service type {0} is not registered with name {1}", serviceType.ToString(), name));return null;}}public T GetService<T>(){if (IsRegistered<T>()){return mobjContainer.Resolve<T>();}else{Type type = typeof(T);ServerLogger.Warn(string.Format("Service type {0} is not registered", type.ToString()));return default(T);}}public T GetService<T>(string name){if (IsRegistered<T>(name)){return mobjContainer.Resolve<T>(name);}else{Type type = typeof(T);ServerLogger.Warn(string.Format("Service type {0} is not registered with name {1}", type.ToString(), name));return default(T);}}public IEnumerable<T> GetServices<T>(){return mobjContainer.ResolveAll<T>();}private bool IsRegistered(Type serviceType, string name=""){if(string.IsNullOrEmpty(name)){return mobjContainer.IsRegistered(serviceType);}else{return mobjContainer.IsRegistered(serviceType, name);}}private bool IsRegistered<T>(string name = ""){if (string.IsNullOrEmpty(name)){return mobjContainer.IsRegistered<T>();}else{return mobjContainer.IsRegistered<T>(name);}}}
?
using log4net;using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net.Repository.Hierarchy;
//Logger
public class ServerLogger{private static ILog Log { get; set; }static ServerLogger(){try{string logFolder = CommonDefine.GetLogPath();if (!Directory.Exists(logFolder)){Directory.CreateDirectory(logFolder);}Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();PatternLayout patternLayout = new PatternLayout();patternLayout.ConversionPattern = "%date{yyyy-MM-dd HH:mm:ss.fff} %level %message%newline";patternLayout.ActivateOptions();RollingFileAppender roller = new RollingFileAppender();roller.AppendToFile = true;roller.File = logFolder + @"\server.log";roller.Layout = patternLayout;roller.MaxSizeRollBackups = 100;roller.RollingStyle = RollingFileAppender.RollingMode.Date;roller.StaticLogFileName = true;roller.ActivateOptions();hierarchy.Configured = true;Logger logger = hierarchy.GetLogger("IMLogger") as Logger;//Log as Logger;logger.Additivity = false;logger.Level = Level.All;logger.AddAppender(roller);Log = LogManager.GetLogger("IMLogger");}catch (Exception){}}public static void Debug(string message){if (CanLog(LogLevel.DEBUG)){Log.Debug(message);}}public static void Info(string message){if (CanLog(LogLevel.INFO)){Log.Info(message);}}public static void Warn(string message){if (CanLog(LogLevel.WARN)){Log.Warn(message);}}public static void Perfomance(Stopwatch watch, string actionName){if (CanLog(LogLevel.PERFORMANCE)){if (watch.IsRunning)watch.Stop();string message = string.Format(actionName + " consumes time {0}", watch.Elapsed.ToString());Log.Info(message);}}public static void Error(string message){if (CanLog(LogLevel.ERROR)){Log.Error(message);}}/* Obsoletepublic static void Fatal(string message){Log.Fatal(message);}*/public static void Error(string message, Exception ex){if (CanLog(LogLevel.ERROR)){Log.Error(message, ex);}}public static void SQL(string sqlScriptMesage){if (CanLog(LogLevel.SQL)){Log.Info(sqlScriptMesage);}}private static bool CanLog(LogLevel level){LogLevel levelConfig = GetLogLevel();return levelConfig >= level;}private static LogLevel GetLogLevel(){LogLevel level = LogLevel.ERROR;try{string logLevel = CommonDefine.GetLogLevel();level = (LogLevel)Enum.Parse(typeof(LogLevel), logLevel.ToUpper());}catch(Exception ex){// Cannot use Error method to avoid stack overflow, use log tool directlyLog.Error("Failed to parse log level setting", ex);}return level;} }public enum LogLevel {ERROR = 1,WARN,INFO,SQL,PERFORMANCE,DEBUG }
3. Infrastructure與Module是一對,Infrastructure用于定義相關接口,而Module用于實現Infrastructure的接口。這也是本文重點介紹的。
?
三、.Infrastructure與.Core
.Infrastructure
.Infrastructure一般可以有4個文件夾:
1. DatabaseContext主要定義與EF相關的操作接口:
//事務操作public interface ITransactionProcessor {void BeginTransaction();void Commit();void Rollback(); } //CRUD
public interface IRepositoryContext : ITransactionProcessor, IDisposable {void Initialize();void Add<T>(T entity) where T : class;void Update<T>(T entity) where T : class;void Delete<T>(T entity) where T : class;void Save(); } //最后定義EF上下文操作接口
public interface IEFRepositoryContext : IRepositoryContext {DbContext Context { get; } }
2. DataContracts主要定義一些業務內需要的數據結構,注意其不引用EF中的ORM對象
3. Repositories主要定義EF中ORM對象集合接口
//定義ORM對象數據集合操作泛型接口,T必須為類public interface IRepository<TEntity> where TEntity : class {/// <summary>/// Return IQueryable without actually query DB /// </summary>/// <param name="expression"></param>/// <param name="includePath"></param>/// <returns></returns>IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> expression = null, params string[] includePath);/// <summary>/// Find the first object which mataches the expression/// </summary>/// <param name="expression"></param>/// <returns></returns>TEntity FirstOrDefault(Expression<Func<TEntity, bool>> expression = null, params string[] includePath);/// <summary>/// Finds an entity with the given primary key values./// The ordering of composite key values is as defined in the EDM/// </summary>/// <param name="keyValues"></param>/// <returns></returns>TEntity FindByKeyValues(params object[] keyValues);/// <summary>/// 根據指定條件表達式得到數據查詢列表/// if no expression, resurn all/// </summary>IList<TEntity> FindList(Expression<Func<TEntity, bool>> expression = null, params string[] includePath);IList<TEntity> FindDistinctList(Expression<Func<TEntity, bool>> expression = null, params string[] includePath);IList<TEntity> FindListByOrder<TKey>(Expression<Func<TEntity, bool>> expression = null, Expression<Func<TEntity, TKey>> orderBy = null, bool ascending = true, params string[] includePath);/// <summary>/// Add entity into DB context/// </summary>/// <param name="entity"></param>void Add(TEntity entity);/// <summary>/// Add a collection of entities/// </summary>/// <param name="entities"></param>void Add(IEnumerable<TEntity> entities);/// <summary>/// 修改實體/// </summary>/// <param name="entity">實體</param>void Update(TEntity entity);/// <summary>/// Update a collection of entities/// </summary>/// <param name="entities"></param>void Update(IEnumerable<TEntity> entities);/// <summary>/// Remove entity by key or keys/// </summary>/// <param name="keyValues"></param>void DeleteByKey(params object[] keyValues);/// <summary>/// Remove entity/// </summary>/// <param name="entity"></param>void Delete(TEntity entity);/// <summary>/// Remove a collection of entities/// </summary>/// <param name="entity"></param>void Delete(IEnumerable<TEntity> entities);/// <summary>/// 分頁獲取全部集合/// </summary>/// <param name="count">返回的記錄總數</param>/// <param name="pageIndex">頁碼</param>/// <param name="pageSize">每頁大小</param>/// <returns>集合</returns>IList<TEntity> LoadPageList<TKey>(out long count, int pageIndex, int pageSize, Expression<Func<TEntity, bool>> expression = null, Expression<Func<TEntity, TKey>> orderBy = null, bool ascending = true, params string[] includePath);IList<TEntity> SqlQueryList(string sqlQueryScript, params object[] parameters); }
//定義一個通過Sql查詢腳本與相關參數得到數據集合的接口
public interface IEntityRepository
{
??? IEnumerable<object> QueryEntities(string sqlQueryScript, params object[] parameters);
}
4. Service主要定義業務邏輯服務接口
?.Core
?Core項目主要是注冊所有接口并且對Infrastructure中定義的接口進行實現。
public class ApplicationService{private static object mobjLock = new object();private static ApplicationService mobjInstance = new ApplicationService();public bool IsInitialized { get; set; }public static ApplicationService Instance{get{return mobjInstance;}}private ApplicationService(){}public void Initialize(){lock (mobjLock){if (IsInitialized)return;// Register all interfaces firstIUnityContainer container = ServiceLocator.Instance.GetContainer();IResourceManagerUtils resourceManager = ServiceLocator.Instance.GetService<IResourceManagerUtils>();resourceManager.InitializeResource("Resource", "SharedResources", System.Globalization.CultureInfo.CurrentCulture, "TestResource");Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();foreach (Assembly assembly in assemblies){// fix bug that GetTypes() of some assembly may throw exception// only allow customized assebmly start with Platform.string assebmlyName = assembly.GetName().Name.ToLower();try{IEnumerable<Type> definedTypes = assembly.GetTypes().Where(t => t.IsClass && !t.IsInterface && !t.IsAbstract);RegisterRepositories(definedTypes);}catch (Exception ex){ServerLogger.Error(string.Format("Failed to load dll {0}", assebmlyName), ex);}}IsInitialized = true;}}private void RegisterRepositories(IEnumerable<Type> definedTypes){IUnityContainer container = ServiceLocator.Instance.GetContainer();Type repositoryInterface = typeof(IRepository<>);Type entityRepositoryInterface = typeof(IEntityRepository);foreach (Type type in definedTypes){Type[] parentIntefaces = type.GetInterfaces();// Is IRepository<T>if (IsGenericTypeOf(type, repositoryInterface)){Type parentInterface = GetParentGenericInterface(repositoryInterface, parentIntefaces);if (parentInterface != null){ServerLogger.Debug(string.Format("Regsiter type {0} to interface {1}", type.FullName, parentInterface.FullName));container.RegisterType(parentInterface, type);}}Attribute[] customAttributes = Attribute.GetCustomAttributes(type, false);if (customAttributes != null){EntityRepositoryAttribute entityRepositoryAtt = customAttributes.FirstOrDefault(a => a is EntityRepositoryAttribute) as EntityRepositoryAttribute;if (entityRepositoryAtt != null){string name = entityRepositoryAtt.EntityClassName;if (!string.IsNullOrEmpty(entityRepositoryAtt.EntityClassName)){// Is IEntityRepositoryif (parentIntefaces.Any(t => t == entityRepositoryInterface)){ServerLogger.Debug(string.Format("Regsiter type {0} to interface {1}", type.FullName, entityRepositoryInterface.FullName));container.RegisterType(entityRepositoryInterface, type, name);}}}}}}private Type GetParentGenericInterface(Type repositoryInterface, Type[] interfaces){if (null == interfaces || 0 == interfaces.Count()){return null;}foreach (var type in interfaces){if (type.IsGenericType &&type.GetGenericTypeDefinition() == repositoryInterface.GetGenericTypeDefinition()){continue;}if (IsGenericTypeOf(type, repositoryInterface)){return type;}}return null;}private Type GetParentInterface(Type repositoryInterface, Type[] interfaces){if (null == interfaces || 0 == interfaces.Count()){return null;}foreach (var type in interfaces){if (IsTypeOf(type, repositoryInterface)){return type;}}return null;}private bool IsGenericTypeOf(Type type, Type genericDefinition){Type[] parameters = null;return IsGenericTypeOf(type, genericDefinition, out parameters);}private bool IsGenericTypeOf(Type type, Type genericDefinition, out Type[] genericParameters){genericParameters = new Type[] { };if (!genericDefinition.IsGenericType){return false;}var isMatch = type.IsGenericType && type.GetGenericTypeDefinition() == genericDefinition.GetGenericTypeDefinition();if (!isMatch && type.BaseType != null){isMatch = IsGenericTypeOf(type.BaseType, genericDefinition, out genericParameters);}if (!isMatch && genericDefinition.IsInterface && type.GetInterfaces().Any()){foreach (var i in type.GetInterfaces()){if (IsGenericTypeOf(i, genericDefinition, out genericParameters)){isMatch = true;break;}}}if (isMatch && !genericParameters.Any()){genericParameters = type.GetGenericArguments();}return isMatch;}private bool IsTypeOf(Type type, Type interfaceDefinition){bool isMatch = false;if (type.BaseType != null){isMatch = IsTypeOf(type.BaseType, interfaceDefinition);}if (!isMatch && interfaceDefinition.IsInterface && type.GetInterfaces().Any()){foreach (var i in type.GetInterfaces()){if (IsTypeOf(i, interfaceDefinition)){isMatch = true;break;}}}return isMatch;}}?
//定義抽象基類,實現兩個接口。public abstract class EFRepository<TEntity> : IEntityRepository, IRepository<TEntity> where TEntity : class{private IEFRepositoryContext mobjContext = null;public IRepositoryContext Context{get { return mobjContext; }}public EFRepository(string contextName = "Default"){IRepositoryContext context = ServiceLocator.Instance.GetService<IRepositoryContext>(contextName) ;if (context is IEFRepositoryContext){mobjContext = context as IEFRepositoryContext;}else{// throw new NotSupportedException();}}public void Add(TEntity entity){mobjContext.Add<TEntity>(entity);}public void Add(IEnumerable<TEntity> entities){foreach (TEntity entity in entities){Add(entity);}}public void Update(TEntity entity){mobjContext.Update<TEntity>(entity);}public void Update(IEnumerable<TEntity> entities){foreach (TEntity entity in entities){Update(entity);}}public void DeleteByKey(params object[] keyValues){TEntity defaultEntity = this.FindByKeyValues(keyValues);if (defaultEntity != null)mobjContext.Delete<TEntity>(defaultEntity);}public void Delete(TEntity entity){mobjContext.Delete<TEntity>(entity);}public void Delete(IEnumerable<TEntity> entities){foreach (TEntity entity in entities){Delete(entity);}}/// <summary>/// /// </summary>/// <param name="expression"></param>/// <param name="includePath">to get related data at one time, EF use latency loading as default</param>/// <returns></returns>public IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> expression = null, params string[] includePath){IQueryable<TEntity> defaultQuery = mobjContext.Context.Set<TEntity>();if (includePath != null){foreach (string path in includePath){if (!string.IsNullOrEmpty(path)){defaultQuery = defaultQuery.Include(path);}}}if (expression != null)defaultQuery = defaultQuery.Where(expression);return defaultQuery;}public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> expression = null, params string[] includePath){IQueryable<TEntity> defaultQuery = Query(expression, includePath);return defaultQuery.FirstOrDefault();}public TEntity FindByKeyValues(params object[] keyValues){return mobjContext.Context.Set<TEntity>().Find(keyValues);}public IList<TEntity> FindList(Expression<Func<TEntity, bool>> expression = null, params string[] includePath){IQueryable<TEntity> defaultQuery = Query(expression, includePath);return defaultQuery.ToList();}public IList<TEntity> FindDistinctList(Expression<Func<TEntity, bool>> expression = null, params string[] includePath){IQueryable<TEntity> defaultQuery = Query(expression, includePath);return defaultQuery.Distinct().ToList();}public IList<TEntity> FindListByOrder<TKey>(Expression<Func<TEntity, bool>> expression = null, Expression<Func<TEntity, TKey>> orderBy = null, bool ascending = true, params string[] includePath){IQueryable<TEntity> defaultQuery = Query(expression, includePath);if (orderBy != null){if (ascending)defaultQuery = defaultQuery.OrderBy(orderBy);elsedefaultQuery = defaultQuery.OrderByDescending(orderBy);}return defaultQuery.ToList();}public IList<TEntity> LoadPageList<TKey>(out long count, int pageIndex, int pageSize, Expression<Func<TEntity, bool>> expression = null, Expression<Func<TEntity, TKey>> orderBy = null, bool ascending = true, params string[] includePath){IQueryable<TEntity> defaultQuery = Query(expression, includePath);if (orderBy != null){if (ascending)defaultQuery = defaultQuery.OrderBy(orderBy);elsedefaultQuery = defaultQuery.OrderByDescending(orderBy);}count = defaultQuery.Count();defaultQuery = defaultQuery.Skip(pageIndex).Take(pageSize);return defaultQuery.ToList();}public IList<TEntity> SqlQueryList(string sqlQueryScript, params object[] parameters){return mobjContext.Context.Set<TEntity>().SqlQuery(sqlQueryScript, parameters).ToList();}public IEnumerable<object> QueryEntities(string sqlQueryScript, params object[] parameters){ServerLogger.Info(string.Format("Query entity by sql {0}", sqlQueryScript));return SqlQueryList(sqlQueryScript, parameters);}
}
?
//實例化EF上下文操作接口
public abstract class EFRepositoryContext : IEFRepositoryContext {protected abstract System.Data.Entity.DbContext GetContext();public System.Data.Entity.DbContext Context{get{return GetContext();}}public virtual void Initialize(){GetContext();}public virtual void Add<T>(T entity) where T : class{if (Context != null){Context.Set<T>().Add(entity);}else{ServerLogger.Warn("Missing DB Context");}}public virtual void Update<T>(T entity) where T : class{if (Context != null){Context.Set<T>().Attach(entity);Context.Entry<T>(entity).State = System.Data.Entity.EntityState.Modified;}else{ServerLogger.Warn("Missing DB Context");}}public virtual void Delete<T>(T entity) where T : class{if (Context != null){Context.Set<T>().Remove(entity);}else{ServerLogger.Warn("Missing DB Context");}}public virtual void Save(){if (Context != null){Context.SaveChanges();}else{ServerLogger.Warn("Missing DB Context");}}public virtual void BeginTransaction(){if (Context != null && Context.Database.CurrentTransaction == null){ServerLogger.Info("Begin Transaction");Context.Database.BeginTransaction();ServerLogger.Info("Transaction started");}}public virtual void Commit(){if (Context != null && Context.Database.CurrentTransaction != null){ServerLogger.Info("Start to Commit");Context.Database.CurrentTransaction.Commit();ServerLogger.Info("Committed");}}public virtual void Rollback(){if (Context != null && Context.Database.CurrentTransaction != null){ServerLogger.Info("Start to rollback");Context.Database.CurrentTransaction.Rollback();ServerLogger.Info("Rollback");}}public virtual void Dispose(){try{if (Context != null){if (Context.Database.CurrentTransaction != null){Context.Database.CurrentTransaction.Dispose();}if (Context.Database.Connection.State != System.Data.ConnectionState.Closed){Context.Database.Connection.Close();}Context.Dispose();}}catch(Exception ex){ServerLogger.Error("Faile to dispose DB context", ex);}} }
?
四、.Infrastructure.Mn與.Module.Mn
.Infrastructure.Mn
當我們完成.Infrastructure項目之后就可以開始寫其它模塊了。
.Infrastructure.Mn需要引用.Infrastructure與Database.EFModel項目。
最簡單的形式如下:
public interface IModuleOneRepository : IRepository<ModuleOneItem> {
//ModuleOneItem為EF的ORM對象! } public interface IModuleOneService {void functionOne();void functionTwo(); }
.Module.Mn
.Module.Mn需要引用.Infrastructure、Database.EFModel以及.Infrastructure.Mn項目
最簡單的形式如下:
public class ModuleOneRepository : EFRepository<ModuleOneItem>, IModuleOneRepository {public ModuleOneRepository(): base(){} } using Microsoft.Practices.Unity;
public class ModuleOneService:BaseModuleService, IModuleOneService {[Dependency]public IModuleOneRepository moduleOneRepository { get; set; }public void functionOne(){}public void functionTwo(){} }
//[Dependency]是Unity依賴注入的屬性注入標簽
?
五、結語
?????? 本篇主要敘述Web項目所需要引用的CoreService相關項目,有了業務核心底層dlls,剩下的就可以在Web項目中進行使用了。Web項目主要使用.Net MVC模式,在我進入項目組時,MVC框架層進行過多次修改。當我有一次拆分完一個模塊的js代碼時,我和另一位Tech Leader以及我們的總架構師都意識到需要進一步優化我們的MVC框架。下篇將帶來Web項目的MVC架構方案以及我們是如何引用本篇的CoreService相關項目!
轉載于:https://www.cnblogs.com/lovecsharp094/p/8722293.html
總結
以上是生活随笔為你收集整理的【.NET架构】BIM软件架构02:Web管控平台后台架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj3669 Meteor Showe
- 下一篇: XMLHttpRequest、fetch