nopCommerce 3.9 大波浪系列 之 网页加载Widgets…
2018-06-22 06:14:07来源:未知 阅读 ()
一.插件简介
插件用于扩展nopCommerce的功能。nopCommerce有几种类型的插件如:支付、税率、配送方式、小部件等(接口如下图),更多插件可以访问nopCommerce官网。
我们看下后台如何配置管理插件的。
【后台管理】【商城配置】【挂件管理】用于配置小部件,【插件管理】【本地插件】管理本地所有插件
nop自带小部件:Nop.Plugin.Widgets.NivoSlider插件用于主页显示幻灯片,后续我们以此插件为例介绍nop是如何加载小部件的
本文主要介绍的是用于在网站中显示的小部件widgets(实现IWidgetPlugin接口)。
只介绍网站中是如何调用的,后续文章中再介绍如何创建IWidgetPlugin插件。
二.小部件介绍及使用
小部件也可以叫小挂件,继承IWidgetPlugin接口。
是用于在网站中显示的小插件。
自带有:Nop.Plugin.Widgets.NivoSlider插件显示幻灯片。如下图红色区域
我们先看下NivoSlider插件文档结构,每一个插件都有一个Description.txt用于插件的描述。
NivoSlider插件Description.txt内容如下图。
SystemName:系统名称唯一。
SupportedVersions: 该插件支持的nop版本号,nop版本号不对可是在插件列表里不显示的。
FileName:程序集dll文件名,一定要和插件生成的dll文件名一样,否则报错
对比后台理解更直观些
安装、卸载插件在后台管理中心进行管理,这里就不多说了。
安装成功的插件会将系统名称保存在项目文件"~/App_Data/InstalledPlugins.txt"中。
三.小部件调用原理分析
我们看网站是如显示小部件的,还是以NivoSlider插件为例.NivoSlider是在首页显示的,打开首页试图“Home\Index.chtml”
我们发现有很多像@Html.Widget("home_page_top")的节点。
@Html.Widget会调用WidgetController控制器下WidgetsByZone方法从而获取显示内容并输出到页面中
1 public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, object additionalData = null, string area = null) 2 { 3 return helper.Action("WidgetsByZone", "Widget", new { widgetZone = widgetZone, additionalData = additionalData, area = area }); 4 }
再来看下小部件中NivoSliderPlugin类,它继承了IWidgetPlugin接口
并实现了IWidgetPlugin接口GetWidgetZones()方法返回显示位置名称集合。
我们发现NivoSlider插件包含了“home_page_top”的位置。
Index.chtml试图中也出现了@Html.Widget("home_page_top"),因此该小部件会在首页中显示。
所以试图中想要使用小部件,使用@Html.Widget("位置名称")就可以了。
ok,知道怎么使用了,我们再看看源码中涉及到哪些相关接口,之间调用关系是怎样的,先上图。
首先WidgetController控制器WidgetsByZone会返回部分视图
1 [ChildActionOnly] 2 public virtual ActionResult WidgetsByZone(string widgetZone, object additionalData = null) 3 { 4 //查找到符合要求的List<RenderWidgetModel> 5 var model = _widgetModelFactory.GetRenderWidgetModels(widgetZone, additionalData); 6 7 //no data? 8 if (!model.Any()) 9 return Content(""); 10 11 return PartialView(model); 12 }
WidgetsByZone.cshtml中代码如下,我们发现这里重新调用了插件中某个action
1 @model List<RenderWidgetModel> 2 @using Nop.Web.Models.Cms; 3 @foreach (var widget in Model) 4 { 5 @Html.Action(widget.ActionName, widget.ControllerName, widget.RouteValues) 6 }
那上边的Action信息又是哪里得到的呢?IWidgetPlugin接口GetDisplayWidgetRoute就是用来返回显示时调用的处理Action信息。
NivoSliderPlugin类实现了GetDisplayWidgetRoute代码如下。
1 /// <summary> 2 /// 获取显示插件的路由 3 /// </summary> 4 /// <param name="widgetZone">Widget zone where it's displayed</param> 5 /// <param name="actionName">Action name</param> 6 /// <param name="controllerName">Controller name</param> 7 /// <param name="routeValues">Route values</param> 8 public void GetDisplayWidgetRoute(string widgetZone, out string actionName, out string controllerName, out RouteValueDictionary routeValues) 9 { 10 actionName = "PublicInfo"; 11 controllerName = "WidgetsNivoSlider"; 12 routeValues = new RouteValueDictionary 13 { 14 {"Namespaces", "Nop.Plugin.Widgets.NivoSlider.Controllers"}, 15 {"area", null}, 16 {"widgetZone", widgetZone} 17 }; 18 }
总结下:WidgetController->WidgetsByZone负责调用显示插件。
而我们开发的小部件需要实现IWidgetPlugin接口GetDisplayWidgetRoute方法告诉上层,我的显示入口是哪个controller 下的action。
下面我们分析下nop是如何找到我们开发的小部件呢?继续看图。
IWidgetModelFactory
GetRenderWidgetModels(widgetZone, additionalData)方法,
传入小部件位置widgetZone(本例中为"home_page_top")获取List<RenderWidgetModel>
IWidgetService
LoadActiveWidgetsByWidgetZone(widgetZone, _workContext.CurrentCustomer, _storeContext.CurrentStore.Id)
负责返回符合要求的IList<IWidgetPlugin>集合,过滤条件为部件位置,用户,商城。
1 /// <summary> 2 /// Load active widgets 3 /// </summary> 4 /// <param name="widgetZone">Widget zone</param> 5 /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param> 6 /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> 7 /// <returns>Widgets</returns> 8 public virtual IList<IWidgetPlugin> LoadActiveWidgetsByWidgetZone(string widgetZone, Customer customer = null, int storeId = 0) 9 { 10 if (String.IsNullOrWhiteSpace(widgetZone)) 11 return new List<IWidgetPlugin>(); 12 13 return LoadActiveWidgets(customer, storeId) 14 .Where(x => x.GetWidgetZones().Contains(widgetZone, StringComparer.InvariantCultureIgnoreCase)).ToList(); 15 } 16 17 /// <summary> 18 /// Load active widgets 19 /// </summary> 20 /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param> 21 /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> 22 /// <returns>Widgets</returns> 23 public virtual IList<IWidgetPlugin> LoadActiveWidgets(Customer customer = null, int storeId = 0) 24 { 25 return LoadAllWidgets(customer, storeId) 26 .Where(x => _widgetSettings.ActiveWidgetSystemNames.Contains(x.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase)).ToList(); 27 } 28 29 /// <summary> 30 /// Load all widgets 31 /// </summary> 32 /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param> 33 /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param> 34 /// <returns>Widgets</returns> 35 public virtual IList<IWidgetPlugin> LoadAllWidgets(Customer customer = null, int storeId = 0) 36 { 37 return _pluginFinder.GetPlugins<IWidgetPlugin>(customer: customer, storeId: storeId).ToList(); 38 }
IPluginFinder
_pluginFinder.GetPlugins<IWidgetPlugin>(customer: customer, storeId: storeId).ToList();
查询继承IWidgetPlugin接口的插件也就是小部件了,这里只返回IWidgetPlugin实现类。
PluginManager
1 using System; 2 using System.Collections.Generic; 3 using System.Configuration; 4 using System.Diagnostics; 5 using System.IO; 6 using System.Linq; 7 using System.Reflection; 8 using System.Threading; 9 using System.Web; 10 using System.Web.Compilation; 11 using Nop.Core.ComponentModel; 12 using Nop.Core.Plugins; 13 14 //Contributor: Umbraco (http://www.umbraco.com). Thanks a lot! 15 //SEE THIS POST for full details of what this does - http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx 16 17 [assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")] 18 namespace Nop.Core.Plugins 19 { 20 /// <summary> 21 /// Sets the application up for the plugin referencing 22 /// </summary> 23 public class PluginManager 24 { 25 #region Const 26 27 private const string InstalledPluginsFilePath = "~/App_Data/InstalledPlugins.txt"; 28 private const string PluginsPath = "~/Plugins"; 29 private const string ShadowCopyPath = "~/Plugins/bin"; 30 31 #endregion 32 33 #region Fields 34 35 private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); 36 private static DirectoryInfo _shadowCopyFolder; 37 private static bool _clearShadowDirectoryOnStartup; 38 39 #endregion 40 41 #region Methods 42 43 /// <summary> 44 /// Returns a collection of all referenced plugin assemblies that have been shadow copied 45 /// </summary> 46 public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; } 47 48 /// <summary> 49 /// Returns a collection of all plugin which are not compatible with the current version 50 /// </summary> 51 public static IEnumerable<string> IncompatiblePlugins { get; set; } 52 53 /// <summary> 54 /// Initialize 55 /// </summary> 56 public static void Initialize() 57 { 58 using (new WriteLockDisposable(Locker)) 59 { 60 // TODO: Add verbose exception handling / raising here since this is happening on app startup and could 61 // prevent app from starting altogether 62 var pluginFolder = new DirectoryInfo(CommonHelper.MapPath(PluginsPath)); 63 _shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(ShadowCopyPath)); 64 65 var referencedPlugins = new List<PluginDescriptor>(); 66 var incompatiblePlugins = new List<string>(); 67 68 _clearShadowDirectoryOnStartup = !String.IsNullOrEmpty(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]) && 69 Convert.ToBoolean(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]); 70 71 try 72 { 73 var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath()); 74 75 Debug.WriteLine("Creating shadow copy folder and querying for dlls"); 76 //ensure folders are created 77 Directory.CreateDirectory(pluginFolder.FullName); 78 Directory.CreateDirectory(_shadowCopyFolder.FullName); 79 80 //get list of all files in bin 81 var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories); 82 if (_clearShadowDirectoryOnStartup) 83 { 84 //clear out shadow copied plugins 85 foreach (var f in binFiles) 86 { 87 Debug.WriteLine("Deleting " + f.Name); 88 try 89 { 90 File.Delete(f.FullName); 91 } 92 catch (Exception exc) 93 { 94 Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc); 95 } 96 } 97 } 98 99 //load description files 100 foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder)) 101 { 102 var descriptionFile = dfd.Key; 103 var pluginDescriptor = dfd.Value; 104 105 //ensure that version of plugin is valid 106 if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase)) 107 { 108 incompatiblePlugins.Add(pluginDescriptor.SystemName); 109 continue; 110 } 111 112 //some validation 113 if (String.IsNullOrWhiteSpace(pluginDescriptor.SystemName)) 114 throw new Exception(string.Format("A plugin '{0}' has no system name. Try assigning the plugin a unique name and recompiling.", descriptionFile.FullName)); 115 if (referencedPlugins.Contains(pluginDescriptor)) 116 throw new Exception(string.Format("A plugin with '{0}' system name is already defined", pluginDescriptor.SystemName)); 117 118 //set 'Installed' property 119 pluginDescriptor.Installed = installedPluginSystemNames 120 .FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null; 121 122 try 123 { 124 if (descriptionFile.Directory == null) 125 throw new Exception(string.Format("Directory cannot be resolved for '{0}' description file", descriptionFile.Name)); 126 //get list of all DLLs in plugins (not in bin!) 127 var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories) 128 //just make sure we're not registering shadow copied plugins 129 .Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName)) 130 .Where(x => IsPackagePluginFolder(x.Directory)) 131 .ToList(); 132 133 //other plugin description info 134 var mainPluginFile = pluginFiles 135 .FirstOrDefault(x => x.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase)); 136 pluginDescriptor.OriginalAssemblyFile = mainPluginFile; 137 138 //shadow copy main plugin file 139 pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile); 140 141 //load all other referenced assemblies now 142 foreach (var plugin in pluginFiles 143 .Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase)) 144 .Where(x => !IsAlreadyLoaded(x))) 145 PerformFileDeploy(plugin); 146 147 //init plugin type (only one plugin per assembly is allowed) 148 foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes()) 149 if (typeof(IPlugin).IsAssignableFrom(t)) 150 if (!t.IsInterface) 151 if (t.IsClass && !t.IsAbstract) 152 { 153 pluginDescriptor.PluginType = t; 154 break; 155 } 156 157 referencedPlugins.Add(pluginDescriptor); 158 } 159 catch (ReflectionTypeLoadException ex) 160 { 161 //add a plugin name. this way we can easily identify a problematic plugin 162 var msg = string.Format("Plugin '{0}'. ", pluginDescriptor.FriendlyName); 163 foreach (var e in ex.LoaderExceptions) 164 msg += e.Message + Environment.NewLine; 165 166 var fail = new Exception(msg, ex); 167 throw fail; 168 } 169 catch (Exception ex) 170 { 171 //add a plugin name. this way we can easily identify a problematic plugin 172 var msg = string.Format("Plugin '{0}'. {1}", pluginDescriptor.FriendlyName, ex.Message); 173 174 var fail = new Exception(msg, ex); 175 throw fail; 176 } 177 } 178 } 179 catch (Exception ex) 180 { 181 var msg = string.Empty; 182 for (var e = ex; e != null; e = e.InnerException) 183 msg += e.Message + Environment.NewLine; 184 185 var fail = new Exception(msg, ex); 186 throw fail; 187 } 188 189 190 ReferencedPlugins = referencedPlugins; 191 IncompatiblePlugins = incompatiblePlugins; 192 193 } 194 } 195 196 /// <summary> 197 /// Mark plugin as installed 198 /// </summary> 199 /// <param name="systemName">Plugin system name</param> 200 public static void MarkPluginAsInstalled(string systemName) 201 { 202 if (String.IsNullOrEmpty(systemName)) 203 throw new ArgumentNullException("systemName"); 204 205 var filePath = CommonHelper.MapPath(InstalledPluginsFilePath); 206 if (!File.Exists(filePath)) 207 using (File.Create(filePath)) 208 { 209 //we use 'using' to close the file after it's created 210 } 211 212 213 var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath()); 214 bool alreadyMarkedAsInstalled = installedPluginSystemNames 215 .FirstOrDefault(x => x.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) != null; 216 if (!alreadyMarkedAsInstalled) 217 installedPluginSystemNames.Add(systemName); 218 PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames,filePath); 219 } 220 221 /// <summary> 222 /// Mark plugin as uninstalled 223 /// </summary> 224 /// <param name="systemName">Plugin system name</param> 225 public static void MarkPluginAsUninstalled(string systemName) 226 { 227 if (String.IsNullOrEmpty(systemName)) 228 throw new ArgumentNullException("systemName"); 229 230 var filePath = CommonHelper.MapPath(InstalledPluginsFilePath); 231 if (!File.Exists(filePath)) 232 using (File.Create(filePath)) 233 { 234 //we use 'using' to close the file after it's created 235 } 236 237 238 var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath()); 239 bool alreadyMarkedAsInstalled = installedPluginSystemNames 240 .FirstOrDefault(x => x.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)) != null; 241 if (alreadyMarkedAsInstalled) 242 installedPluginSystemNames.Remove(systemName); 243 PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames,filePath); 244 } 245 246 /// <summary> 247 /// Mark plugin as uninstalled 248 /// </summary> 249 public static void MarkAllPluginsAsUninstalled() 250 { 251 var filePath = CommonHelper.MapPath(InstalledPluginsFilePath); 252 if (File.Exists(filePath)) 253 File.Delete(filePath); 254 } 255 256 #endregion 257 258 #region Utilities 259 260 /// <summary> 261 /// Get description files 262 /// </summary> 263 /// <param name="pluginFolder">Plugin directory info</param> 264 /// <returns>Original and parsed description files</returns> 265 private static IEnumerable<KeyValuePair<FileInfo, PluginDescriptor>> GetDescriptionFilesAndDescriptors(DirectoryInfo pluginFolder) 266 { 267 if (pluginFolder == null) 268 throw new ArgumentNullException("pluginFolder"); 269 270 //create list (<file info, parsed plugin descritor>) 271 var result = new List<KeyValuePair<FileInfo, PluginDescriptor>>(); 272 //add display order and path to list 273 foreach (var descriptionFile in pluginFolder.GetFiles("Description.txt", SearchOption.AllDirectories)) 274 { 275 if (!IsPackagePluginFolder(descriptionFile.Directory)) 276 continue; 277 278 //parse file 279 var pluginDescriptor = PluginFileParser.ParsePluginDescriptionFile(descriptionFile.FullName); 280 281 //populate list 282 result.Add(new KeyValuePair<FileInfo, PluginDescriptor>(descriptionFile, pluginDescriptor)); 283 } 284 285 //sort list by display order. NOTE: Lowest DisplayOrder will be first i.e 0 , 1, 1, 1, 5, 10 286 //it's required: http://www.nopcommerce.com/boards/t/17455/load-plugins-based-on-their-displayorder-on-startup.aspx 287 result.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder)); 288 return result; 289 } 290 291 /// <summary> 292 /// Indicates whether assembly file is already loaded 293 /// </summary> 294 /// <param name="fileInfo">File info</param> 295 /// <returns>Result</returns> 296 private static bool IsAlreadyLoaded(FileInfo fileInfo) 297 { 298 //compare full assembly name 299 //var fileAssemblyName = AssemblyName.GetAssemblyName(fileInfo.FullName); 300 //foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) 301 //{ 302 // if (a.FullName.Equals(fileAssemblyName.FullName, StringComparison.InvariantCultureIgnoreCase)) 303 // return true; 304 //} 305 //return false; 306 307 //do not compare the full assembly name, just filename 308 try 309 { 310 string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.FullName); 311 if (fileNameWithoutExt == null) 312 throw new Exception(string.Format("Cannot get file extension for {0}", fileInfo.Name)); 313 foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) 314 { 315 string assemblyName = a.FullName.Split(new[] { ',' }).FirstOrDefault(); 316 if (fileNameWithoutExt.Equals(assemblyName, StringComparison.InvariantCultureIgnoreCase)) 317 return true; 318 } 319 } 320 catch (Exception exc) 321 { 322 Debug.WriteLine("Cannot validate whether an assembly is already loaded. " + exc); 323 } 324 return false; 325 } 326 327 /// <summary> 328 /// Perform file deply 329 /// </summary> 330 /// <param name="plug">Plugin file info</param> 331 /// <returns>Assembly</returns> 332 private static Assembly PerformFileDeploy(FileInfo plug) 333 { 334 if (plug.Directory == null || plug.Directory.Parent == null) 335 throw new InvalidOperationException("The plugin directory for the " + plug.Name + " file exists in a folder outside of the allowed nopCommerce folder hierarchy"); 336 337 FileInfo shadowCopiedPlug; 338 339 if (CommonHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) 340 { 341 //all plugins will need to be copied to ~/Plugins/bin/ 342 //this is absolutely required because all of this relies on probingPaths being set statically in the web.config 343 344 //were running in med trust, so copy to custom bin folder 345 var shadowCopyPlugFolder = Directory.CreateDirectory(_shadowCopyFolder.FullName); 346 shadowCopiedPlug = InitializeMediumTrust(plug, shadowCopyPlugFolder); 347 } 348 else 349 { 350 var directory = AppDomain.CurrentDomain.DynamicDirectory; 351 Debug.WriteLine(plug.FullName + " to " + directory); 352 //were running in full trust so copy to standard dynamic folder 353 shadowCopiedPlug = InitializeFullTrust(plug, new DirectoryInfo(directory)); 354 } 355 356 //we can now register the plugin definition 357 var shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName)); 358 359 //add the reference to the build manager 360 Debug.WriteLine("Adding to BuildManager: '{0}'", shadowCopiedAssembly.FullName); 361 BuildManager.AddReferencedAssembly(shadowCopiedAssembly); 362 363 return shadowCopiedAssembly; 364 } 365 366 /// <summary> 367 /// Used to initialize plugins when running in Full Trust 368 /// </summary> 369 /// <param name="plug"></param> 370 /// <param name="shadowCopyPlugFolder"></param> 371 /// <returns></returns> 372 private static FileInfo InitializeFullTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder) 373 { 374 var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name)); 375 try 376 { 377 File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); 378 } 379 catch (IOException) 380 { 381 Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename"); 382 //this occurs when the files are locked, 383 //for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them 384 //which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy 385 try 386 { 387 var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old"; 388 File.Move(shadowCopiedPlug.FullName, oldFile); 389 } 390 catch (IOException exc) 391 { 392 throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc); 393 } 394 //ok, we've made it this far, now retry the shadow copy 395 File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); 396 } 397 return shadowCopiedPlug; 398 } 399 400 /// <summary> 401 /// Used to initialize plugins when running in Medium Trust 402 /// </summary> 403 /// <param name="plug"></param> 404 /// <param name="shadowCopyPlugFolder"></param> 405 /// <returns></returns> 406 private static FileInfo InitializeMediumTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder) 407 { 408 var shouldCopy = true; 409 var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name)); 410 411 //check if a shadow copied file already exists and if it does, check if it's updated, if not don't copy 412 if (shadowCopiedPlug.Exists) 413 { 414 //it's better to use LastWriteTimeUTC, but not all file systems have this property 415 //maybe it is better to compare file hash? 416 var areFilesIdentical = shadowCopiedPlug.CreationTimeUtc.Ticks >= plug.CreationTimeUtc.Ticks; 417 if (areFilesIdentical) 418 { 419 Debug.WriteLine("Not copying; files appear identical: '{0}'", shadowCopiedPlug.Name); 420 shouldCopy = false; 421 } 422 else 423 { 424 //delete an existing file 425 426 //More info: http://www.nopcommerce.com/boards/t/11511/access-error-nopplugindiscountrulesbillingcountrydll.aspx?p=4#60838 427 Debug.WriteLine("New plugin found; Deleting the old file: '{0}'", shadowCopiedPlug.Name); 428 File.Delete(shadowCopiedPlug.FullName); 429 } 430 } 431 432 if (shouldCopy) 433 { 434 try 435 { 436 File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); 437 } 438 catch (IOException) 439 { 440 Debug.WriteLine(shadowCopiedPlug.FullName + " is locked, attempting to rename"); 441 //this occurs when the files are locked, 442 //for some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them 443 //which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy 444 try 445 { 446 var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old"; 447 File.Move(shadowCopiedPlug.FullName, oldFile); 448 } 449 catch (IOException exc) 450 { 451 throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc); 452 } 453 //ok, we've made it this far, now retry the shadow copy 454 File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); 455 } 456 } 457 458 return shadowCopiedPlug; 459 } 460 461 /// <summary> 462 /// Determines if the folder is a bin plugin folder for a package 463 /// </summary> 464 /// <param name="folder"></param> 465 /// <returns></returns> 466 private static bool IsPackagePluginFolder(DirectoryInfo folder) 467 { 468 if (folder == null) return false; 469 if (folder.Parent == null) return false; 470 if (!folder.Parent.Name.Equals("Plugins", StringComparison.InvariantCultureIgnoreCase)) return false; 471 return true; 472 } 473 474 /// <summary> 475 /// Gets the full path of InstalledPlugins.txt file 476 /// </summary> 477 /// <returns></returns> 478 private static string GetInstalledPluginsFilePath() 479 { 480 return CommonHelper.MapPath(InstalledPluginsFilePath); 481 } 482 483 #endregion 484 } 485 } 486
PluginManager.ReferencedPlugins获取到所有的插件。
PluginManager类用于管理插件,我们发现[assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]表示应用程序启动就调用PluginManager类下边的Initialize方法进行初始化。初始化过程中会把所有插件保存到ReferencedPlugins变量中。
经过上边一层层的过滤查找,终于找到了符合要求的插件了并保存在IList<IWidgetPlugin>集合中。最后经过IWidgetModelFactory接口GetRenderWidgetModels(widgetZone, additionalData)方法处理保存为List<RenderWidgetModel>最后为WidgetsByZone试图使用。
ps: 调用关系描述的不是很清晰,请见谅,大家还是看图,结合代码理解吧。
四.总结
1.nop支持各种插件,不同插件继承接口不一样
2.小部件继承IWidgetPlugin,用于在网页中显示。
3.IWidgetPlugin接口 IList<string> GetWidgetZones()返回显示部件的位置名称集合。GetDisplayWidgetRoute方法返回显示插件时的路由。
4.网站视图中用@Html.Widget("位置名称")来显示插件。”位置名称”包含在插件GetWidgetZones()返回的集合中。
文中有错误的理解和不正确的观点请指正、留言、一起交流共同进步。
本文地址:http://www.cnblogs.com/yaoshangjin/p/7239183.html
本文为大波浪原创、转载请注明出处。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- NopCommerce学习(1) Caching 2018-07-20
- 【NopCommerce源码架构学习-二】单例模式实现代码分析 2018-06-22
- nopCommerce 3.9 大波浪系列 之 使用部署在Docker中的Redis 2018-06-22
- 抽取nopCommerce实现插件化的代码用于ASP.NETMVC的插件化出 2018-06-22
- nopCommerce 3.9 大波浪系列 之 可退款的支付宝插件(下) 2018-06-22
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash