NOP源码分析六--实体、数据的分层与处理。
也许我应该先研究下架构,但现在还是接着研究NOP吧,不能半途而废啊。
之前粗略研究过如何添加一个属性,http://www.cnblogs.com/runit/p/3842611.html,基本一样。我们以ScheduleTask为例。
下面是一个实体类,没有多余功能,就是提供了实体类的各个属性。
namespace Nop.Core.Domain.Tasks { public class ScheduleTask : BaseEntity { /// <summary> /// Gets or sets the name /// </summary> public string Name { get; set; } /// <summary> /// Gets or sets the run period (in seconds) /// </summary> public int Seconds { get; set; } /// <summary> /// Gets or sets the type of appropriate ITask class /// </summary> public string Type { get; set; } /// <summary> /// Gets or sets the value indicating whether a task is enabled /// </summary> public bool Enabled { get; set; } /// <summary> /// Gets or sets the value indicating whether a task should be stopped on some error /// </summary> public bool StopOnError { get; set; } public DateTime? LastStartUtc { get; set; } public DateTime? LastEndUtc { get; set; } public DateTime? LastSuccessUtc { get; set; } } }
下面就是关系映射类,这个类就用到了上面的类,把上面的类映射到数据库相应的表名,以及Key是哪一个,哪些属性不能为空,以及长度上面的都在这里设定。
前面我们提到的NopObjectContext(DBContext) 就是通过反射得到所有的NopEntityTypeConfiguration子类,并进行增删改查的。
namespace Nop.Data.Mapping.Tasks { public partial class ScheduleTaskMap : NopEntityTypeConfiguration<ScheduleTask> { public ScheduleTaskMap() { this.ToTable("ScheduleTask"); this.HasKey(t => t.Id); this.Property(t => t.Name).IsRequired(); this.Property(t => t.Type).IsRequired(); } } }
上面2个是底层的,下面我们介绍界面层的。代码如下:
namespace Nop.Admin.Models.Tasks { [Validator(typeof(ScheduleTaskValidator))] public partial class ScheduleTaskModel : BaseNopEntityModel { [NopResourceDisplayName("Admin.System.ScheduleTasks.Name")] [AllowHtml] public string Name { get; set; } [NopResourceDisplayName("Admin.System.ScheduleTasks.Seconds")] public int Seconds { get; set; } [NopResourceDisplayName("Admin.System.ScheduleTasks.Enabled")] public bool Enabled { get; set; } [NopResourceDisplayName("Admin.System.ScheduleTasks.StopOnError")] public bool StopOnError { get; set; } [NopResourceDisplayName("Admin.System.ScheduleTasks.LastStart")] public string LastStartUtc { get; set; } [NopResourceDisplayName("Admin.System.ScheduleTasks.LastEnd")] public string LastEndUtc { get; set; } [NopResourceDisplayName("Admin.System.ScheduleTasks.LastSuccess")] public string LastSuccessUtc { get; set; } } }
这里我们看到,这个类做了Nop.Core.Domain实体类的相同属性,也就是重复了,这应该就是为了解耦、分离。
同时他还有NopResourceDisplayName属性,用于告诉界面如何显示,AllowHtml,是否允许HTML等。
在类的开始 有如下代码:[Validator(typeof(ScheduleTaskValidator))] ,这个应该就是验证类的了。我们看Ixia这个类
namespace Nop.Admin.Validators.Tasks { public class ScheduleTaskValidator : BaseNopValidator<ScheduleTaskModel> { public ScheduleTaskValidator(ILocalizationService localizationService) { RuleFor(x => x.Name).NotEmpty().WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Name.Required")); RuleFor(x => x.Seconds).GreaterThan(0).WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Seconds.Positive")); } } }
果然是验证代码,如果验证失败,则返回什么等。用到的验证是FluentValidation,http://www.cnblogs.com/whitewolf/archive/2012/05/27/2520593.html
http://www.cnblogs.com/jes_shaw/p/3380940.html,上面是学习的连接,验证,及验证集成类的代码如下,就是一个FluentValidation验证。
namespace Nop.Admin.Validators.Tasks { public class ScheduleTaskValidator : BaseNopValidator<ScheduleTaskModel> { public ScheduleTaskValidator(ILocalizationService localizationService) { RuleFor(x => x.Name).NotEmpty().WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Name.Required")); RuleFor(x => x.Seconds).GreaterThan(0).WithMessage(localizationService.GetResource("Admin.System.ScheduleTasks.Seconds.Positive")); } } }
继承自如下类,如下类继承自FluentValidation的标准验证抽象类,实现构造内验证。
namespace Nop.Web.Framework.Validators { public abstract class BaseNopValidator<T> : AbstractValidator<T> where T : class { protected BaseNopValidator() { PostInitialize(); } /// <summary> /// Developers can override this method in custom partial classes /// in order to add some custom initialization code to constructors /// </summary> protected virtual void PostInitialize() { } } }
我们注意到,验证类用到了ILocalizationService类,通过DependencyRegistrar类查到其实现类是LocalizationService,这个类还挺复杂,慢慢来。
namespace Nop.Services.Localization { /// <summary> /// Provides information about localization /// </summary> public partial class LocalizationService : ILocalizationService { #region Constants /// <summary> /// Key for caching /// </summary> /// <remarks> /// {0} : language ID /// </remarks> private const string LOCALSTRINGRESOURCES_ALL_KEY = "Nop.lsr.all-{0}"; /// <summary> /// Key for caching /// </summary> /// <remarks> /// {0} : language ID /// {1} : resource key /// </remarks> private const string LOCALSTRINGRESOURCES_BY_RESOURCENAME_KEY = "Nop.lsr.{0}-{1}"; /// <summary> /// Key pattern to clear cache /// </summary> private const string LOCALSTRINGRESOURCES_PATTERN_KEY = "Nop.lsr."; #endregion #region Fields private readonly IRepository<LocaleStringResource> _lsrRepository; private readonly IWorkContext _workContext; private readonly ILogger _logger; private readonly ILanguageService _languageService; private readonly ICacheManager _cacheManager; private readonly IDataProvider _dataProvider; private readonly IDbContext _dbContext; private readonly CommonSettings _commonSettings; private readonly LocalizationSettings _localizationSettings; private readonly IEventPublisher _eventPublisher; #endregion #region Ctor /// <summary> /// Ctor /// </summary> /// <param name="cacheManager">Cache manager</param> /// <param name="logger">Logger</param> /// <param name="workContext">Work context</param> /// <param name="lsrRepository">Locale string resource repository</param> /// <param name="languageService">Language service</param> /// <param name="dataProvider">Data provider</param> /// <param name="dbContext">Database Context</param> /// <param name="commonSettings">Common settings</param> /// <param name="localizationSettings">Localization settings</param> /// <param name="eventPublisher">Event published</param> public LocalizationService(ICacheManager cacheManager, ILogger logger, IWorkContext workContext, IRepository<LocaleStringResource> lsrRepository, ILanguageService languageService, IDataProvider dataProvider, IDbContext dbContext, CommonSettings commonSettings, LocalizationSettings localizationSettings, IEventPublisher eventPublisher) { this._cacheManager = cacheManager; this._logger = logger; this._workContext = workContext; this._lsrRepository = lsrRepository; this._languageService = languageService; this._dataProvider = dataProvider; this._dbContext = dbContext; this._commonSettings = commonSettings; this._dataProvider = dataProvider; this._dbContext = dbContext; this._commonSettings = commonSettings; this._localizationSettings = localizationSettings; this._eventPublisher = eventPublisher; } #endregion #region Methods /// <summary> /// Deletes a locale string resource /// </summary> /// <param name="localeStringResource">Locale string resource</param> public virtual void DeleteLocaleStringResource(LocaleStringResource localeStringResource) { if (localeStringResource == null) throw new ArgumentNullException("localeStringResource"); _lsrRepository.Delete(localeStringResource); //cache _cacheManager.RemoveByPattern(LOCALSTRINGRESOURCES_PATTERN_KEY); //event notification _eventPublisher.EntityDeleted(localeStringResource); } /// <summary> /// Gets a locale string resource /// </summary> /// <param name="localeStringResourceId">Locale string resource identifier</param> /// <returns>Locale string resource</returns> public virtual LocaleStringResource GetLocaleStringResourceById(int localeStringResourceId) { if (localeStringResourceId == 0) return null; return _lsrRepository.GetById(localeStringResourceId); } /// <summary> /// Gets a locale string resource /// </summary> /// <param name="resourceName">A string representing a resource name</param> /// <returns>Locale string resource</returns> public virtual LocaleStringResource GetLocaleStringResourceByName(string resourceName) { if (_workContext.WorkingLanguage != null) return GetLocaleStringResourceByName(resourceName, _workContext.WorkingLanguage.Id); return null; } /// <summary> /// Gets a locale string resource /// </summary> /// <param name="resourceName">A string representing a resource name</param> /// <param name="languageId">Language identifier</param> /// <param name="logIfNotFound">A value indicating whether to log error if locale string resource is not found</param> /// <returns>Locale string resource</returns> public virtual LocaleStringResource GetLocaleStringResourceByName(string resourceName, int languageId, bool logIfNotFound = true) { var query = from lsr in _lsrRepository.Table orderby lsr.ResourceName where lsr.LanguageId == languageId && lsr.ResourceName == resourceName select lsr; var localeStringResource = query.FirstOrDefault(); if (localeStringResource == null && logIfNotFound) _logger.Warning(string.Format("Resource string ({0}) not found. Language ID = {1}", resourceName, languageId)); return localeStringResource; } /// <summary> /// Gets all locale string resources by language identifier /// </summary> /// <param name="languageId">Language identifier</param> /// <returns>Locale string resources</returns> public virtual IList<LocaleStringResource> GetAllResources(int languageId) { var query = from l in _lsrRepository.Table orderby l.ResourceName where l.LanguageId == languageId select l; var locales = query.ToList(); return locales; } /// <summary> /// Inserts a locale string resource /// </summary> /// <param name="localeStringResource">Locale string resource</param> public virtual void InsertLocaleStringResource(LocaleStringResource localeStringResource) { if (localeStringResource == null) throw new ArgumentNullException("localeStringResource"); _lsrRepository.Insert(localeStringResource); //cache _cacheManager.RemoveByPattern(LOCALSTRINGRESOURCES_PATTERN_KEY); //event notification _eventPublisher.EntityInserted(localeStringResource); } /// <summary> /// Updates the locale string resource /// </summary> /// <param name="localeStringResource">Locale string resource</param> public virtual void UpdateLocaleStringResource(LocaleStringResource localeStringResource) { if (localeStringResource == null) throw new ArgumentNullException("localeStringResource"); _lsrRepository.Update(localeStringResource); //cache _cacheManager.RemoveByPattern(LOCALSTRINGRESOURCES_PATTERN_KEY); //event notification _eventPublisher.EntityUpdated(localeStringResource); } /// <summary> /// Gets all locale string resources by language identifier /// </summary> /// <param name="languageId">Language identifier</param> /// <returns>Locale string resources</returns> public virtual Dictionary<string, KeyValuePair<int,string>> GetAllResourceValues(int languageId) { string key = string.Format(LOCALSTRINGRESOURCES_ALL_KEY, languageId); return _cacheManager.Get(key, () => { //we use no tracking here for performance optimization //anyway records are loaded only for read-only operations var query = from l in _lsrRepository.TableNoTracking orderby l.ResourceName where l.LanguageId == languageId select l; var locales = query.ToList(); //format: <name, <id, value>> var dictionary = new Dictionary<string, KeyValuePair<int, string>>(); foreach (var locale in locales) { var resourceName = locale.ResourceName.ToLowerInvariant(); if (!dictionary.ContainsKey(resourceName)) dictionary.Add(resourceName, new KeyValuePair<int, string>(locale.Id, locale.ResourceValue)); } return dictionary; }); } /// <summary> /// Gets a resource string based on the specified ResourceKey property. /// </summary> /// <param name="resourceKey">A string representing a ResourceKey.</param> /// <returns>A string representing the requested resource string.</returns> public virtual string GetResource(string resourceKey) { if (_workContext.WorkingLanguage != null) return GetResource(resourceKey, _workContext.WorkingLanguage.Id); return ""; } /// <summary> /// Gets a resource string based on the specified ResourceKey property. /// </summary> /// <param name="resourceKey">A string representing a ResourceKey.</param> /// <param name="languageId">Language identifier</param> /// <param name="logIfNotFound">A value indicating whether to log error if locale string resource is not found</param> /// <param name="defaultValue">Default value</param> /// <param name="returnEmptyIfNotFound">A value indicating whether an empty string will be returned if a resource is not found and default value is set to empty string</param> /// <returns>A string representing the requested resource string.</returns> public virtual string GetResource(string resourceKey, int languageId, bool logIfNotFound = true, string defaultValue = "", bool returnEmptyIfNotFound = false) { string result = string.Empty; if (resourceKey == null) resourceKey = string.Empty; resourceKey = resourceKey.Trim().ToLowerInvariant(); if (_localizationSettings.LoadAllLocaleRecordsOnStartup) { //load all records (we know they are cached) var resources = GetAllResourceValues(languageId); if (resources.ContainsKey(resourceKey)) { result = resources[resourceKey].Value; } } else { //gradual loading string key = string.Format(LOCALSTRINGRESOURCES_BY_RESOURCENAME_KEY, languageId, resourceKey); string lsr = _cacheManager.Get(key, () => { var query = from l in _lsrRepository.Table where l.ResourceName == resourceKey && l.LanguageId == languageId select l.ResourceValue; return query.FirstOrDefault(); }); if (lsr != null) result = lsr; } if (String.IsNullOrEmpty(result)) { if (logIfNotFound) _logger.Warning(string.Format("Resource string ({0}) is not found. Language ID = {1}", resourceKey, languageId)); if (!String.IsNullOrEmpty(defaultValue)) { result = defaultValue; } else { if (!returnEmptyIfNotFound) result = resourceKey; } } return result; } /// <summary> /// Export language resources to xml /// </summary> /// <param name="language">Language</param> /// <returns>Result in XML format</returns> public virtual string ExportResourcesToXml(Language language) { if (language == null) throw new ArgumentNullException("language"); var sb = new StringBuilder(); var stringWriter = new StringWriter(sb); var xmlWriter = new XmlTextWriter(stringWriter); xmlWriter.WriteStartDocument(); xmlWriter.WriteStartElement("Language"); xmlWriter.WriteAttributeString("Name", language.Name); var resources = GetAllResources(language.Id); foreach (var resource in resources) { xmlWriter.WriteStartElement("LocaleResource"); xmlWriter.WriteAttributeString("Name", resource.ResourceName); xmlWriter.WriteElementString("Value", null, resource.ResourceValue); xmlWriter.WriteEndElement(); } xmlWriter.WriteEndElement(); xmlWriter.WriteEndDocument(); xmlWriter.Close(); return stringWriter.ToString(); } /// <summary> /// Import language resources from XML file /// </summary> /// <param name="language">Language</param> /// <param name="xml">XML</param> public virtual void ImportResourcesFromXml(Language language, string xml) { if (language == null) throw new ArgumentNullException("language"); if (String.IsNullOrEmpty(xml)) return; if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduredSupported) { //SQL 2005 insists that your XML schema incoding be in UTF-16. //Otherwise, you'll get "XML parsing: line 1, character XXX, unable to switch the encoding" //so let's remove XML declaration var inDoc = new XmlDocument(); inDoc.LoadXml(xml); var sb = new StringBuilder(); using (var xWriter = XmlWriter.Create(sb, new XmlWriterSettings { OmitXmlDeclaration = true })) { inDoc.Save(xWriter); xWriter.Close(); } var outDoc = new XmlDocument(); outDoc.LoadXml(sb.ToString()); xml = outDoc.OuterXml; //stored procedures are enabled and supported by the database. var pLanguageId = _dataProvider.GetParameter(); pLanguageId.ParameterName = "LanguageId"; pLanguageId.Value = language.Id; pLanguageId.DbType = DbType.Int32; var pXmlPackage = _dataProvider.GetParameter(); pXmlPackage.ParameterName = "XmlPackage"; pXmlPackage.Value = xml; pXmlPackage.DbType = DbType.Xml; //long-running query. specify timeout (600 seconds) _dbContext.ExecuteSqlCommand("EXEC [LanguagePackImport] @LanguageId, @XmlPackage", false, 600, pLanguageId, pXmlPackage); } else { //stored procedures aren't supported var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xml); var nodes = xmlDoc.SelectNodes(@"//Language/LocaleResource"); foreach (XmlNode node in nodes) { string name = node.Attributes["Name"].InnerText.Trim(); string value = ""; var valueNode = node.SelectSingleNode("Value"); if (valueNode != null) value = valueNode.InnerText; if (String.IsNullOrEmpty(name)) continue; //do not use "Insert"/"Update" methods because they clear cache //let's bulk insert var resource = language.LocaleStringResources.FirstOrDefault(x => x.ResourceName.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (resource != null) resource.ResourceValue = value; else { language.LocaleStringResources.Add( new LocaleStringResource { ResourceName = name, ResourceValue = value }); } } _languageService.UpdateLanguage(language); } //clear cache _cacheManager.RemoveByPattern(LOCALSTRINGRESOURCES_PATTERN_KEY); } #endregion } }
好复杂,不是么?我们看一下他的构造如下:
public LocalizationService(ICacheManager cacheManager, ILogger logger, IWorkContext workContext, IRepository<LocaleStringResource> lsrRepository, ILanguageService languageService, IDataProvider dataProvider, IDbContext dbContext, CommonSettings commonSettings, LocalizationSettings localizationSettings, IEventPublisher eventPublisher) { this._cacheManager = cacheManager; this._logger = logger; this._workContext = workContext; this._lsrRepository = lsrRepository; this._languageService = languageService; this._dataProvider = dataProvider; this._dbContext = dbContext; this._commonSettings = commonSettings; this._dataProvider = dataProvider; this._dbContext = dbContext; this._commonSettings = commonSettings; this._localizationSettings = localizationSettings; this._eventPublisher = eventPublisher; }
东西太多,比如:缓存管理、日志、工作上下文、存储仓库、数据提供、。。。。很多我们都没有研究,看一下就行了。我们略过这里。用到什么再研究也不晚。我们找到GetResource方法:
/// <summary> /// Gets a resource string based on the specified ResourceKey property. /// </summary> /// <param name="resourceKey">A string representing a ResourceKey.</param> /// <returns>A string representing the requested resource string.</returns> public virtual string GetResource(string resourceKey) { if (_workContext.WorkingLanguage != null) return GetResource(resourceKey, _workContext.WorkingLanguage.Id); return ""; }
用到了IWorkContext 查找依赖注册的是:WebWorkContext,查看代码 构造函数依然复杂,略过。。。。查看WorkingLanguage方法。
/// <summary> /// Get or set current user working language /// </summary> public virtual Language WorkingLanguage { get { if (_cachedLanguage != null) return _cachedLanguage; Language detectedLanguage = null; if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { //get language from URL detectedLanguage = GetLanguageFromUrl(); } if (detectedLanguage == null && _localizationSettings.AutomaticallyDetectLanguage) { //get language from browser settings //but we do it only once if (!this.CurrentCustomer.GetAttribute<bool>(SystemCustomerAttributeNames.LanguageAutomaticallyDetected, _genericAttributeService, _storeContext.CurrentStore.Id)) { detectedLanguage = GetLanguageFromBrowserSettings(); if (detectedLanguage != null) { _genericAttributeService.SaveAttribute(this.CurrentCustomer, SystemCustomerAttributeNames.LanguageAutomaticallyDetected, true, _storeContext.CurrentStore.Id); } } } if (detectedLanguage != null) { //the language is detected. now we need to save it if (this.CurrentCustomer.GetAttribute<int>(SystemCustomerAttributeNames.LanguageId, _genericAttributeService, _storeContext.CurrentStore.Id) != detectedLanguage.Id) { _genericAttributeService.SaveAttribute(this.CurrentCustomer, SystemCustomerAttributeNames.LanguageId, detectedLanguage.Id, _storeContext.CurrentStore.Id); } } var allLanguages = _languageService.GetAllLanguages(storeId: _storeContext.CurrentStore.Id); //find current customer language var languageId = this.CurrentCustomer.GetAttribute<int>(SystemCustomerAttributeNames.LanguageId, _genericAttributeService, _storeContext.CurrentStore.Id); var language = allLanguages.FirstOrDefault(x => x.Id == languageId); if (language == null) { //it not specified, then return the first (filtered by current store) found one language = allLanguages.FirstOrDefault(); } if (language == null) { //it not specified, then return the first found one language = _languageService.GetAllLanguages().FirstOrDefault(); } //cache _cachedLanguage = language; return _cachedLanguage; } set { var languageId = value != null ? value.Id : 0; _genericAttributeService.SaveAttribute(this.CurrentCustomer, SystemCustomerAttributeNames.LanguageId, languageId, _storeContext.CurrentStore.Id); //reset cache _cachedLanguage = null; } }
开始是缓存,先略过吧。LocalizationSettings在依赖注册里竟然没有。。。。断了。。