Several of you tried the tip in Speeding-up DNN Module Development and emailed to report that this does work, but on occasion, the assemblies in the private assembly folder are locked during development. I did some testing to find the cause and a solution.

Background: Assemblies in the “bin” folder are automatically shadow copied to a different folder before the ASP.net process loads them. This allows the files in the “bin” folder to be replaced without causing any locking issues. According to this article even assemblies specified in AppDomainSetup.PrivateBinPath are shadow copied. Of course, this raised the question — does PrivateBinPath get initialized from the web.config element.

My curiosity piqued, I did some testing to find the answer. The test is simple enough — delete the existing shadow copy folder for an app (typically in C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\{appname}); setup with a private path and move some assemblies there; run the app. Assemblies that are shadow copied will then show up in a sub-directory of this folder. In my test, the files from the path were not shadow copied (i.e. AppDomainSetup.PrivateBinPath is not initialized from element). But does this explain why the assemblies are locked sometimes? Not completely.

Typically, garbage collection is able to determine if an assembly should be unloaded if there are no more references to its contents. You can ensure that the unload attempt happens right away with GC.Collect(). However, if references to the assembly exist, then GC will not unload the assembly, and since there is no shadow copy, the result is a locked file. If you are recompiling in VS.Net and the file is still locked, it may indicate that there is a problem with your code … i.e. something has a reference to “something” in your assembly after the ASP.Net page execution is complete. Or, it may not be your code, but VS.Net that has locked the assembly.

I tested with several assemblies that were being used on the page, but were not in the bin folder. Even though these assemblies were not shadow copied, I did not have a problem with them being locked (i.e. an attempt to delete or rename the file works). I could compile/debug etc. with no problem. I suppose it’s only a matter of time before I encounter this issue. Is this one of those VS.Net quirks?

Bottom line, there is no quick fix. The “iisreset” command will take care of the locked DLL problem, but then it re-creates the problem that keeping the assemblies in the folder was trying to solve in the first place (i.e. app restart delay). If it does not happen too often, then keeping a command window open and typing iisreset may be the simplest fix (for now).

Working through this problem, I had a thought. What if DNN were to manage the assembly loading and unloading for modules? This would not only allow assemblies to be placed in their respective module folders, but would also solve the problem of DNN DLL-Hell. During development, it would also solve the problem of assemblies being locked as the assembly shadow copying could be enabled when the assembly is loaded on-the-fly.

I don’t know the answer, but it’s an experiment I’ll add to my To-Do list.

Testing an implementation of the DNN ISearchable interface implementation for a module can be time-consuming and slow if you rely on the DNN search engine indexer to run and then either check the database for results or use the Search UI. There is a simpler way.

Copy and paste the below script into DNNSearch.aspx (or grab the attachment at the end of this post), place the file in the root folder of your app and you can test ISearchable for any module instance with ease. The script requires you to provide a TabId and a ModuleId. It then queries the database for the BusinessController defined in the DesktopModules table and instantiates it exactly as the DNN search indexer does. It then calls GetSearchItems() and displays the results.

Unlike the DNN search indexer, DNNSearch does not make any changes to the database. It is useful only for testing if the ISearchable implementation is working correctly and does not provide any insights into any issues that the search index provider you are using may have.

 

DNNSearch.aspx

<%@ Import namespace="DotNetNuke.Entities.Modules" %>
<%@ Import namespace="DotNetNuke.Services.Search" %>
<%@ Import namespace="DotNetNuke.Common" %>
<%@ Page Language="c#" AutoEventWireup="false" %>
<script runat="server">
 
    void Results_Click(object sender, EventArgs e)
    {
        int moduleId = -1;
        try
        {
            moduleId = Convert.ToInt32(ModuleId.Text);
        }
        catch
        {
        }
 
        int tabId = -1;
        try
        {
            tabId = Convert.ToInt32(TabId.Text);
        }
        catch
        {
        }
 
        if ((moduleId > -1) && (tabId > -1))
            GetSearchResults(moduleId, tabId);
        else
            SearchResults.Text = "Both Module ID and Tab ID are required";
    }
 
    void GetSearchResults(int moduleId, int tabId)
    {
        ModuleController moduleController = new ModuleController();
        ModuleInfo moduleInfo = moduleController.GetModule(moduleId, tabId);
        StringBuilder sb = new StringBuilder();
 
        if (moduleInfo == null) 
        {
            SearchResults.Text = "No module found with ModuleID=" + moduleId.ToString() + " and TabID=" + tabId.ToString();
            return;
        }
 
        if (moduleInfo.BusinessControllerClass == "")
            SearchResults.Text = "The BusinessControllerClass in the database is blank.";
        else
        {
            try
            {
                object bizController = DotNetNuke.Framework.Reflection.CreateObject(moduleInfo.BusinessControllerClass, moduleInfo.BusinessControllerClass);
                if (bizController == null)
                    SearchResults.Text = "The Business Controller Class " + moduleInfo.BusinessControllerClass + " could not be instantiated.";
                else
                {    
                    SearchContentModuleInfo contentInfo = new SearchContentModuleInfo();
                    contentInfo.ModControllerType = (ISearchable) bizController;
                    contentInfo.ModInfo = moduleInfo;
                    SearchItemInfoCollection results = contentInfo.ModControllerType.GetSearchItems(contentInfo.ModInfo);
                    if (results != null)
                    {
                        int counter = 0;
                        foreach(SearchItemInfo searchItem in results)
                        {
                    if (moduleInfo.ModuleID == searchItem.ModuleId)
                    {
                        sb.Append("

Title: " + searchItem.Title);
                        sb.Append("GUID: " + searchItem.GUID);
                        sb.Append("Date: " + searchItem.PubDate.ToLongDateString());
                        sb.Append("Description: " + searchItem.Description + "

");
                    }
                    counter++;
                        }
                        SearchResults.Text = counter.ToString() + " results found." + sb.ToString();
                    }
                    else
                        SearchResults.Text = "No search results.";
                }
            }
            catch(Exception e)
            {
                SearchResults.Text = "Error: " + e.Message + "

" + e.StackTrace;
            }
        }
    }
 
    override protected void OnInit(EventArgs e)
    {
        Results.Click += new EventHandler(Results_Click);
        base.OnInit(e);
    }
 
script>
 
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
    <HEAD>
        <title>Speerio ISearchable Testtitle>
        <style>
              body, p {font-family: Verdana; font-size: 9pt}
        style>
    HEAD>
    <body>
        <p><font size="4">DNNSearch Script - by <a href="http://www.speerio.net">Speerio, Inc.a>p>
        <p><font color="red" size="4">WARNING: Do not leave this script installed on a production system.font>p>
        <form id="Form1" method="post" runat="server">
            <p>Tab ID: <asp:TextBox ID="TabId" Runat="server">asp:TextBox>p>
            <p>Module ID: <asp:TextBox ID="ModuleId" Runat="server">asp:TextBox>p>
            <asp:Button ID="Results" Runat="server" Text="Get Search Results" />p>
            <p>To test for user-specific results, add code to GetSearchItems() to check for userid=N in querystring.p>
            <p><b>Search Results:b>p>
            <asp:Label ID="SearchResults" Runat="server" />
        form>
    body>
HTML>

DNNSearch.zip (1.48 KB)

Wouldn’t you know it…I post a spoof about Web 2.0 Design Guidelines and the very next day discover a site that dynamically generates Web 2.0 logos. Here’s the devTao Web 2.0 logo rendered by the site:

 

Get your own Web 2.0 logo here: http://msig.info/web2.php

Note to startups — When coming-up with marketing language for your site, pay attention or you will end-up with self-fulfilling prophecies. “Kiko is a great, dead simple calendar” might not have been the most appropriate choice of words for Kiko.

On the DotNetNuke forums today, jstemper posed a question about how to speed up DNN development, specifically, the delay caused by app re-start when a module is recompiled.

I have invested a considerable amount of time researching the intricacies of Fusion probing to faciliate the co-existence of third-party assemblies with different versions in the same bin folder. In an earlier post on Managing assembly versions in ASP.Net I had provided tips on doing this. Reading this post got me thinking about applying the same technique for DNN development. I did a quick test and everything seems to work. Here is how you can speed-up DNN module development by skipping the app restart that occurs when assemblies in the “bin” folder are updated.

1) Create a “bin” folder under ~/DesktopModules (i.e. ~/DesktopModules/bin)

2) Modify your DNN web.config as follows:

            <runtime>
              <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
                   <probing privatePath="bin;DesktopModules\bin" />
              assemblyBinding>
           runtime>

If you have a section already present, just add the element, otherwise, you can just add this whole block right before .

The “privatePath” attribute tells Fusion where to search for assemblies referenced by an application.

3) Change your module’s script (ascx) file so the “Inherits” attribute includes the assembly name like this:

Inherits=”Speerio.DNN.Modules.SkinStudio.Editor, Speerio.DNN.Modules.SkinStudio”

This corresponds to a typename of “Speerio.DNN.Modules.SkinStudio.Editor” and an assembly file “Speerio.DNN.Modules.SkinStudio.dll” (note: the extension should not be included in the “Inherits” attribute value).

4) Change your module’s VS.Net project build folder to ~/DesktopModules/bin

That’s it. Now, when you recompile your module, there will be no application restart and the only assemblies that are converted from bytecode to native code are your module assemblies.

Please post a comment if you encounter any problems so I can modify the procedure if necessary.

© 2012 TechBubble Suffusion theme by Sayontan Sinha