nopCommerce 3.9 大波浪系列 之 網頁載入Widgets插件原理

来源:http://www.cnblogs.com/yaoshangjin/archive/2017/07/26/7239183.html
-Advertisement-
Play Games

nopCommerce 3.9 中插件類型整理,詳細介紹 IWidgetPlugin 插件顯示原理,載入流程分析。 ...


一.插件簡介

       插件用於擴展nopCommerce的功能。nopCommerce有幾種類型的插件如:支付、稅率、配送方式、小部件等(介面如下圖),更多插件可以訪問nopCommerce官網

image

  我們看下後臺如何配置管理插件的。

【後臺管理】【商城配置】【掛件管理】用於配置小部件,【插件管理】【本地插件】管理本地所有插件

image

nop自帶小部件:Nop.Plugin.Widgets.NivoSlider插件用於主頁顯示幻燈片,後續我們以此插件為例介紹nop是如何載入小部件的

image

本文主要介紹的是用於在網站中顯示的小部件widgets(實現IWidgetPlugin介面)。

只介紹網站中是如何調用的,後續文章中再介紹如何創建IWidgetPlugin插件。

二.小部件介紹及使用

小部件也可以叫小掛件,繼承IWidgetPlugin介面。

是用於在網站中顯示的小插件。

自帶有:Nop.Plugin.Widgets.NivoSlider插件顯示幻燈片。如下圖紅色區域

)SDY2NP6`%C7LZM_N1LXFRA

我們先看下NivoSlider插件文檔結構,每一個插件都有一個Description.txt用於插件的描述。

image 

NivoSlider插件Description.txt內容如下圖。

SystemName:系統名稱唯一。

SupportedVersions: 該插件支持的nop版本號,nop版本號不對可是在插件列表裡不顯示的。

FileName:程式集dll文件名,一定要和插件生成的dll文件名一樣,否則報錯

image

對比後臺理解更直觀些

image

安裝、卸載插件在後臺管理中心進行管理,這裡就不多說了。

安裝成功的插件會將系統名稱保存在項目文件"~/App_Data/InstalledPlugins.txt"中。

三.小部件調用原理分析

      我們看網站是如顯示小部件的,還是以NivoSlider插件為例.NivoSlider是在首頁顯示的,打開首頁試圖“Home\Index.chtml”

image 

我們發現有很多像@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介面

image

並實現了IWidgetPlugin介面GetWidgetZones()方法返回顯示位置名稱集合。

我們發現NivoSlider插件包含了“home_page_top”的位置。

Index.chtml試圖中也出現了@Html.Widget("home_page_top"),因此該小部件會在首頁中顯示。

所以試圖中想要使用小部件,使用@Html.Widget("位置名稱")就可以了。

image

ok,知道怎麼使用了,我們再看看源碼中涉及到哪些相關介面,之間調用關係是怎樣的,先上圖。

Widget載入時序圖

首先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是如何找到我們開發的小部件呢?繼續看圖。
Widget載入時序圖

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                 <

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 很多同學可能對於CAP這個項目想有更一步的瞭解,包括她的工作原理以及適用的場景,所以博主就準備了一場直播給大家講解了一下,這個視頻是直播的一個錄像。 由於我這次直播本來是沒有打算對外的,所以也是沒有怎麼準備的,所以在直播的過程中出現了一些講解不到位或者是意外的情況,還請大家能夠諒解~。 最後, ...
  • 背水一戰 Windows 10 之 控制項(媒體類): InkCanvas 塗鴉編輯 ...
  • ASP.NET MVC ,一個適用於WEB應用程式的經典模型 model-view-controller 模式。相對於web forms一個單一的整塊,asp.net mvc是由連接在一起的各種代碼層所組成。 最近又接觸了關於asp.net mvc的項目,又重拾以前的記憶,感覺忘了好多,特此記錄。 ...
  • 建立一個 SignalR 連接 配置 SignalR 連接 在 WPF 客戶端里設置連接的最大值 設置 Query String 參數 讀取 query string 指定傳輸協議 可以指定以下四種方式 LongPollingTransport ServerSentEventsTransport W ...
  • 心得體會:沒事谷歌谷歌 ...
  • 如何將鍵盤快捷方式映射到自定義按鈕,怎麼使用快捷鍵啟動自己創建的菜單,剛開始做的時候迷糊了,找了很久。可能也是因為剛開始做不是很明白,後面慢慢就懂了。其實非常簡單的。 很多快捷鍵已經在Visual studio中使用的,在確定快捷鍵之前驗證下你想設置的快捷鍵是否可以使用,當然是不可以重覆啦,是吧。 ...
  • 每個控制項都有自己預設的模板,這是MS本身就編寫好的,如果我們能夠得到這些模板的XAML代碼,那麼它將是學習模板的最好的示例,要想獲得某個控制項ctrl的預設模板,請調用以下方法: ...
  • SignalR 的 generated proxy 服務端 JavaScript 客戶端 generated proxy 非 generated proxy 什麼時候使用 generated proxy 如果你要給客戶端的方法註冊多個事件處理器,那麼你就不能使用 generated proxy。如果 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...