Well,
I struggled with the bin folder. The problem (if we can say “problem”) is that the packaging process simply packs what it “copies to the directory” set to “copy if new or always” only for the web application project (web role). The presence of other assemblies in the BIN that do not explicitly reference the web application will not be deployed.
In my case, when I have pretty “static” links, I just pack them in a ZIP, put them in a BLOB container, and then use the Azure Bootstrapper to load, extract and put these links in the BIN folder. However, since I do not know the actual location of the BIN folder in the startup task, I use helper shells for the bootloader to do the trick.
You will need to get a list of local sites that can be done with:
public IEnumerable<string> WebSiteDirectories { get { string roleRootDir = Environment.GetEnvironmentVariable("RdRoleRoot"); string appRootDir = (RoleEnvironment.IsEmulated) ? Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) : roleRootDir; XDocument roleModelDoc = XDocument.Load(Path.Combine(roleRootDir, "RoleModel.xml")); var siteElements = roleModelDoc.Root.Element(_roleModelNs + "Sites").Elements(_roleModelNs + "Site"); return from siteElement in siteElements where siteElement.Attribute("name") != null && siteElement.Attribute("name").Value == "Web" && siteElement.Attribute("physicalDirectory") != null select Path.Combine(appRootDir, siteElement.Attribute("physicalDirectory").Value); } }
If the variable _roleModelNs is defined as follows:
private readonly XNamespace _roleModelNs = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition";
Next, you'll need something similar to this method:
public void GetRequiredAssemblies(string pathToWebBinfolder) { string args = string.Join("", @"-get https://your_account.blob.core.windows.net/path/to/plugin.zip -lr $lr(temp) -unzip """, pathToWebBinfolder, @""" -block"); this._bRunner.RunBootstrapper(args); }
And RunBootstrapper has the following signature:
public bool RunBootstrapper (string args) { bool result = false; ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = this._bootstrapperPath; psi.Arguments = args; Trace.WriteLine("AS: Calling " + psi.FileName + " " + psi.Arguments + " ..."); psi.CreateNoWindow = true; psi.ErrorDialog = false; psi.UseShellExecute = false; psi.WindowStyle = ProcessWindowStyle.Hidden; psi.RedirectStandardOutput = true; psi.RedirectStandardInput = false; psi.RedirectStandardError = true; // run elevated // psi.Verb = "runas"; try { // Start the process with the info we specified. // Call WaitForExit and then the using statement will close. using (Process exeProcess = Process.Start(psi)) { exeProcess.PriorityClass = ProcessPriorityClass.High; string outString = string.Empty; // use ansynchronous reading for at least one of the streams // to avoid deadlock exeProcess.OutputDataReceived += (s, e) => { outString += e.Data; }; exeProcess.BeginOutputReadLine(); // now read the StandardError stream to the end // this will cause our main thread to wait for the // stream to close string errString = exeProcess.StandardError.ReadToEnd(); Trace.WriteLine("Process out string: " + outString); Trace.TraceError("Process error string: " + errString); result = true; } } catch (Exception e) { Trace.TraceError("AS: " + e.Message + e.StackTrace); result = false; } return result; }
Of course, in your case, you may need something more complicated, in which you first try to extract all the plugins (if each plugin is in its own ZIP) through the code, and then execute GetRequiredAssemblies several times for each plugin. And this code can be executed in the RoleEntryPoint OnStart method.
And also, if you plan more dynamically, you can also override the Run () method of your RoleEntryPoint subclass and, for example, check for new plugins every minute.
Hope this helps!
EDIT
And how can you connect plugins. Well, you can manually load your plugins, or you can develop a small custom BuildTask to automatically load the plugin during assembly.