<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-32442424</id><updated>2012-05-16T20:36:02.527+01:00</updated><category term='TIFF'/><category term='Vista'/><category term='widgetbox'/><category term='Image'/><category term='Visual Studio 2005'/><category term='Freegle'/><category term='tag'/><category term='msitran'/><category term='gadget'/><category term='Windows'/><category term='settings'/><category term='open source'/><category term='applet'/><category term='MSI'/><category term='Sort'/><category term='live spaces'/><category term='Ajax'/><category term='Forms authentication'/><category term='Multiboot'/><category term='ASP.NET'/><category term='live.com'/><category term='module'/><category term='asp:HyperLink'/><category term='Visual Studio 2008'/><category term='accessibility'/><category term='Web Setup'/><category term='orca'/><category term='liquid'/><category term='tag-aware'/><category term='SQL server'/><category term='virtual tour'/><category term='DNN'/><category term='Apache'/><category term='mashup'/><category term='JSON'/><category term='vtiger'/><category term='IE7'/><category term='text size'/><category term='Merge'/><category term='boot'/><category term='XMP'/><category term='multi-boot'/><category term='CSS'/><category term='cookies'/><category term='JPEG'/><category term='EasyBCD'/><category term='Find-in-files'/><category term='tabindex'/><category term='font'/><category term='Java'/><category term='cookieless'/><category term='IIS'/><category term='Google'/><category term='.net string UTF8'/><category term='VS2008'/><category term='Drupal'/><category term='Tomcat'/><category term='photo'/><category term='integration'/><category term='custom'/><category term='Array'/><category term='.NET ASP.NET'/><category term='DotNetNuke'/><category term='skin'/><category term='netvibes'/><category term='VPS'/><category term='CMS'/><category term='JavaScript Object Notation'/><category term='custom module'/><category term='IE'/><category term='BCD'/><category term='Internet Explorer'/><category term='Ubuntu'/><category term='command line'/><category term='ViewState'/><category term='Custom Action'/><category term='maps'/><category term='JavaScript'/><category term='widget'/><category term='.NET'/><title type='text'>Chris Cant's developer blog</title><subtitle type='html'>Here are some programming tips, be it ASP.NET, C#, CSS, Java, JavaScript, PHP, SQL, XHTML, etc.   I am director of PHD Computer Consultants Ltd, based in Cumbria, England, UK - we sell our own software and undertake software projects and consultancy.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default?start-index=26&amp;max-results=25'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>54</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-32442424.post-1126784059521484273</id><published>2011-08-04T09:22:00.007+01:00</published><updated>2011-08-04T10:03:25.966+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>Using PHDCC CodeModule with javascript</title><content type='html'>phdcc Director &lt;i&gt;John Cant&lt;/i&gt; writes:&lt;br /&gt;&lt;br /&gt;My use of DNN has become - &lt;br /&gt;&lt;br /&gt;[a] Maintain any HTML in a text file / copy and paste it into the basic editor / convert to raw / save. (Online editors are too hard to use and keep slim)&lt;br /&gt;&lt;br /&gt;[b] Put any functionality into a &lt;a href="http://www.phdcc.com/phdcc.CodeModule/"&gt;phdcc.CodeModule&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Much valuable functionality - jquery-ui / google maps / facebook / janrain / plupload / galleria - is available as javascript libraries. To get them onto a DNN Page, I have created a template CodeModule as follows.&lt;br /&gt;&lt;br /&gt;ASP.NET gives you limited control over where in the page javascript can be inserted - RegisterClientScriptBlock inserts somewhere near the top; RegisterStartupScript somewhere near the bottom. All I can say is - the template works on all browsers tested.&lt;br /&gt;&lt;br /&gt;In this example, I load the jquery-ui library at the top, and daisy chain the DNN onload handler at the bottom. The javascript could of course be located in a separate file.&lt;br /&gt;&lt;br /&gt;In the onload handler, I create an invisible empty jquery-ui dialog and initialise a jquery-ui accordion. I bind the submit button of a PHDCC form to a handler and examine one of the form fields to give feedback via the dialog.&lt;br /&gt;&lt;br /&gt;I add the CodeModule somewhere out of the way on the page - the bottom pane is a good place. Once there, accordions and dialogs can be used anywhere on the page.&lt;br /&gt;&lt;br /&gt;If you need any help with this or any of the suggested libraries, please get back to us.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;%@ Control Language="C#" ClassName="JCformWidgets" %&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;script runat="server"&amp;gt;&lt;br /&gt;&lt;br /&gt;//--------------------------------------&lt;br /&gt;private void loadJS()&lt;br /&gt;{&lt;br /&gt; string scriptKey = "JCjsIncludes:" + this.UniqueID;&lt;br /&gt; if( Page.IsStartupScriptRegistered( scriptKey) || Page.IsPostBack) return;&lt;br /&gt;&lt;br /&gt; ClientScriptManager cs = Page.ClientScript;&lt;br /&gt; Type cstype = this.GetType();&lt;br /&gt;&lt;br /&gt; // ----------------------------&lt;br /&gt; // load JS files at the top of the page&lt;br /&gt; // ----------------------------&lt;br /&gt; string scriptBlock = "\n";&lt;br /&gt;&lt;br /&gt; scriptBlock += "&amp;lt;link type='text/css' href='/jqui/jquery-ui-1.8.14.custom.css' rel='Stylesheet'&amp;gt;\n";&lt;br /&gt; scriptBlock += "&amp;lt;script src='/jqui/jquery-ui-1.8.14.custom.min.js' type='text/javascript'&amp;gt;";&lt;br /&gt; scriptBlock += "&amp;lt;/";&lt;br /&gt; scriptBlock += "script&amp;gt;\n";  // avoid microsoft parsing bug&lt;br /&gt;&lt;br /&gt; cs.RegisterClientScriptBlock( cstype, scriptKey, scriptBlock);&lt;br /&gt;&lt;br /&gt; // ----------------------------&lt;br /&gt; // load onload handler etc. at the bottom of the page&lt;br /&gt; // ----------------------------&lt;br /&gt; string startupScript = &lt;br /&gt;  @"&amp;lt;script language='JavaScript'&amp;gt;&lt;br /&gt;&amp;lt;!--&lt;br /&gt;&lt;br /&gt;function JConload(){&lt;br /&gt;&lt;br /&gt; var oldOnLoad = window.onload;&lt;br /&gt;&lt;br /&gt; var myDialog = $('&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;').dialog( {autoOpen: false} );&lt;br /&gt;   &lt;br /&gt; this.newOnload = function(){&lt;br /&gt;  &lt;br /&gt;  // call existing handler&lt;br /&gt;  if(typeof oldOnLoad === 'function'){ &lt;br /&gt;   oldOnLoad();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  // -------------------------------------&lt;br /&gt;&lt;br /&gt;  $(function() {&lt;br /&gt;   $( '#accordion' ).accordion();&lt;br /&gt;  });&lt;br /&gt;  &lt;br /&gt;  // -------------------------------------&lt;br /&gt;&lt;br /&gt;  $('#' + 'dnn_ctr422_ViewForm_btnSubmit').bind('click', function() {&lt;br /&gt;&lt;br /&gt;   var theseSkills = $( '#' + 'dnn_ctr422_ViewForm_Group_157_400_Question_157_400_1563_form_157_text_1563').val();&lt;br /&gt;   var thisDialogText = '';&lt;br /&gt; &lt;br /&gt;   if( theseSkills.length &amp;lt;= 0) {&lt;br /&gt;    thisDialogText = 'No skills given&amp;lt;br&amp;gt;Please try again.';&lt;br /&gt;&lt;br /&gt;   myDialog.html( thisDialogText);&lt;br /&gt;    myDialog.dialog( 'option', 'title', 'Things you forgot ...' );&lt;br /&gt;  &lt;br /&gt;    myDialog.dialog( 'open');&lt;br /&gt;    return false;&lt;br /&gt;   }&lt;br /&gt;  });&lt;br /&gt;&lt;br /&gt;  // -------------------------------------&lt;br /&gt;&lt;br /&gt; };&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;(function(){&lt;br /&gt; var JCOL = new JConload();&lt;br /&gt; window.onload = JCOL.newOnload;&lt;br /&gt;}());&lt;br /&gt;&lt;br /&gt;            // --&amp;gt;";&lt;br /&gt;&lt;br /&gt; startupScript += "&amp;lt;/sc";&lt;br /&gt; startupScript += "ript&amp;gt;"; // avoid microsoft parsing bug&lt;br /&gt;&lt;br /&gt; cs.RegisterStartupScript( cstype, scriptKey, startupScript);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;//--------------------------------------&lt;br /&gt;protected void Page_Load(object sender, EventArgs e)&lt;br /&gt;{&lt;br /&gt; loadJS();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt; &lt;br /&gt; &lt;br /&gt; &lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-1126784059521484273?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/1126784059521484273/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=1126784059521484273' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1126784059521484273'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1126784059521484273'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2011/08/using-phdcc-codemodule-with-javascript.html' title='Using PHDCC CodeModule with javascript'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-3885495399612717613</id><published>2011-05-12T11:17:00.000+01:00</published><updated>2011-05-13T21:36:26.957+01:00</updated><title type='text'>Scheduled task ping for plesk in Windows</title><content type='html'>I've been moving several domains to a Windows cloud hosting package at &lt;a href="http://www.webhosting.uk.com/"&gt;http://www.webhosting.uk.com/&lt;/a&gt;. I'm currently using Cloud Beginner hosting that gives 10 domains and a dedicated IP address for £12/month. This package lets us run DotNetNuke (DNN) sites - and fast too as there is not much contention on the included SQL Server databases. The cloud is UK based so the ping time is less from the UK and nearby.&lt;br /&gt;&lt;br /&gt;The account is run using Windows plesk which lets you control most things. However intervention is often needed from the webhosting.uk.com staff. They are usually very responsive and competent on their Live Chat service. Typical requests include setting ASP.NET modify permissions for the Network Service user and setting Application Starting Points. You need to insist that you get the dedicated IP address if you've asked for it. Email is included using the Horde web client, which isn't great; perhaps using Google Apps for email might be the best solution. No web stats package is included. Nor are file or database backups.&lt;br /&gt;&lt;br /&gt;For one domain, one task that I needed to do was to set up scheduled tasks to ping various URLs. While this could be done externally, I decided to use the Plesk scheduled task feature. When FTPing in, the main site is at /httpdocs/. The scheduled task VBS scripts should be placed in /cgi-bin/&lt;br /&gt;&lt;br /&gt;Using a couple of resources on the Internet, I put together this script, called &lt;span style="font-family:courier new;"&gt;pingurls.vbs&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;Call DoScheduledTask()&lt;br /&gt;&lt;br /&gt;Sub DoScheduledTask()&lt;br /&gt; On Error Resume Next&lt;br /&gt;&lt;br /&gt; Const ForReading = 1&lt;br /&gt;&lt;br /&gt; Set objFSO = CreateObject("Scripting.FileSystemObject")&lt;br /&gt; Set objFile = objFSO.OpenTextFile("E:\inetpub\vhosts\mydomainname.com\cgi-bin\urls2ping.txt", ForReading)&lt;br /&gt;&lt;br /&gt; Dim URL&lt;br /&gt; Do Until objFile.AtEndOfStream&lt;br /&gt;  URL = objFile.ReadLine&lt;br /&gt;  if Len(URL)&amp;gt;0 then PingURL(URL)&lt;br /&gt; Loop&lt;br /&gt; objFile.Close&lt;br /&gt;&lt;br /&gt;end sub&lt;br /&gt;&lt;br /&gt;sub PingURL(URL)&lt;br /&gt;&lt;br /&gt; Dim RequestObj&lt;br /&gt; Set RequestObj = CreateObject("Microsoft.XMLHTTP")&lt;br /&gt; 'Open request and pass the URL&lt;br /&gt; RequestObj.open "POST", URL , false&lt;br /&gt; 'Send Request&lt;br /&gt; RequestObj.Send&lt;br /&gt; 'cleanup&lt;br /&gt; Set RequestObj = Nothing&lt;br /&gt;&lt;br /&gt;End Sub&lt;/pre&gt;&lt;br /&gt;This script reads a text file &lt;span style="font-family:courier new;"&gt;urls2ping.txt&lt;/span&gt;, also in the /cgi-bin/ directory. Each line contains a URL to be pinged.&lt;br /&gt;&lt;br /&gt;How do you know what absolute path to use to get to the text file? This ASP.NET code, say in MyPath.aspx, will tell you the path it is running from:&lt;br /&gt;&lt;pre&gt;&amp;lt;%@ Page Language="VB" %&amp;gt;&lt;br /&gt;&amp;lt;script runat="server"&amp;gt;&lt;br /&gt;Sub Page_Load()&lt;br /&gt; lblPath.Text = Request.MapPath("~")&lt;br /&gt;End Sub&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;html&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;Web app path using Request.MapPath&amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt; &amp;lt;asp:Label ID="lblPath" Runat="server" /&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The final stage is to set up the scheduled task in plesk. This is straight forward using [Scheduled Tasks] then [Add New Task]. The Path to Executable file should be similar to that used above, ie &lt;span style="font-family:courier new;"&gt;E:\inetpub\vhosts\mydomainname.com\cgi-bin\pingurls.vbs&lt;/span&gt;. Set the times you want the task to run.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-3885495399612717613?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/3885495399612717613/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=3885495399612717613' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/3885495399612717613'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/3885495399612717613'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2011/05/scheduled-task-ping-for-plesk-in.html' title='Scheduled task ping for plesk in Windows'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-2765377585418045764</id><published>2010-11-20T09:14:00.012Z</published><updated>2010-11-20T15:34:47.803Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='VPS'/><category scheme='http://www.blogger.com/atom/ns#' term='Tomcat'/><category scheme='http://www.blogger.com/atom/ns#' term='Apache'/><category scheme='http://www.blogger.com/atom/ns#' term='Ubuntu'/><title type='text'>Installing Tomcat onto Ubuntu Apache2</title><content type='html'>&lt;p&gt;This post describes how to install Tomcat 6 on an Ubuntu 10.10 server and connect it up with the Apache2 server using the jk module. Although there are plenty of posts about this, none gave a full list of instructions; this is what I aim to provide - let me know if you have any corrections.&lt;/p&gt;&lt;p&gt;I assume that you have Apache2 installed.&lt;/p&gt;&lt;p&gt;You will need to do most of the following actions at a root shell prompt. Get this from your login using: &lt;code&gt;sudu su -&lt;/code&gt; You will need to edit various text files. You can do this using vi at the shell prompt. However I usually do this on my local computer using my preferred editor, transferring files to and fro using FTP. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;Installing Tomcat&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This command gets Tomcat 6 - and Java if need be:&lt;br /&gt;&lt;code&gt;aptitude install tomcat6&lt;/code&gt;&lt;/p&gt;&lt;p&gt;I'm not sure if that gets all the Tomcat files, so enter this to get them all:&lt;br /&gt;&lt;code&gt;apt-get install tomcat6 tomcat6-admin tomcat6-common tomcat6-user tomcat6-docs tomcat6-examples&lt;/code&gt;&lt;/p&gt;&lt;p&gt;On my system, Java was installed to here: &lt;code&gt;/usr/lib/jvm/default-java/&lt;/code&gt;&lt;/p&gt;&lt;p&gt;You now need to set up Java environment variables in text script file &lt;code&gt;/etc/environment&lt;/code&gt;&lt;br /&gt;Add this to the PATH variable: &lt;code&gt;/usr/lib/jvm/default-java/bin&lt;/code&gt; after a colon separator. Add these lines:&lt;/p&gt;&lt;pre&gt;JAVA_HOME=/usr/lib/jvm/default-java&lt;br /&gt;JDK_HOME=/usr/lib/jvm/default-java&lt;/pre&gt;&lt;p&gt;You will now need to reboot so these changes are used.&lt;/p&gt;&lt;p&gt;Tomcat should now be running. You can start, stop or restart it as follows:&lt;/p&gt;&lt;pre&gt;/etc/init.d/tomcat6 stop&lt;br /&gt;/etc/init.d/tomcat6 status&lt;br /&gt;/etc/init.d/tomcat6 restart&lt;/pre&gt;&lt;p&gt;or more easily like this:&lt;/p&gt;&lt;pre&gt;service tomcat6 restart&lt;/pre&gt;&lt;p&gt;Tomcat should now be running on port 8080 at your host, eg http://www.example.com:8080/  If you go there, you should see an &lt;strong&gt;It works&lt;/strong&gt; page. To test it further, I put a JSP file in here: &lt;code&gt;/var/lib/tomcat6/webapps/ROOT/&lt;/code&gt; and saw that it ran OK. I then put a servlet WAR file in &lt;code&gt;/var/lib/tomcat6/webapps/&lt;/code&gt;. This was soon expanded automatically by Tomcat and I was able to navigate to the servlet directory (still on the 8080 port).&lt;/p&gt;&lt;p&gt;You may find it useful to run the &lt;strong&gt;manager&lt;/strong&gt; and &lt;strong&gt;host-manager&lt;/strong&gt; Tomcat applications. To enable these you need to edit the Tomcat configuration files in &lt;code&gt;/etc/tomcat6/&lt;/code&gt;. Edit &lt;code&gt;tomcat-users.xml&lt;/code&gt; to include the following:&lt;/p&gt; &lt;pre&gt;&amp;lt;role rolename="manager"/&amp;gt;&lt;br /&gt;&amp;lt;role rolename="admin"/&amp;gt;&lt;br /&gt;&amp;lt;user name="admin" password="secret_password" roles="manager,admin"/&amp;gt;&lt;/pre&gt;&lt;p&gt;You should now be able to access these apps at /manager/html and /host-manager/html. You will need to enter your credentials; possibly a couple of times on first access.&lt;/p&gt;&lt;p&gt;The Tomcat logs are at &lt;code&gt;/var/log/tomcat6/&lt;/code&gt;. By default Tomcat creates a different log file for each day. Keep an eye on these, and the disk space they consume.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Connecting Tomcat to Apache&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Hopefully you already know these commands to test your Apache configuration and restart Apache. You'll need these later.&lt;/p&gt;&lt;pre&gt;apache2ctl configtest&lt;br /&gt;apache2ctl restart&lt;/pre&gt;&lt;p&gt;First, tell Tomcat to listen out for connections from Apache. Edit &lt;code&gt;/etc/tomcat6/server.xml&lt;/code&gt; to uncomment this line: &lt;/p&gt;&lt;pre&gt;&amp;lt;Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /&amp;gt;&lt;/pre&gt;&lt;p&gt;Restart Tomcat. &lt;/p&gt;&lt;p&gt;Install the &lt;strong&gt;jk&lt;/strong&gt; module that acts as the connector between Apache and Tomcat:&lt;br /&gt;&lt;code&gt;apt-get install libapache2-mod-jk&lt;/code&gt;&lt;/p&gt;&lt;p&gt;You should see a file called &lt;code&gt;jk.load&lt;/code&gt; in &lt;code&gt;/etc/apache2/mods-available/&lt;/code&gt;. I edited this to contain:&lt;/p&gt;&lt;pre&gt;LoadModule jk_module /usr/lib/apache2/modules/mod_jk.so&lt;br /&gt;JkWorkersFile /etc/libapache2-mod-jk/workers.properties&lt;br /&gt;JkLogFile /var/log/apache2/mod_jk.log&lt;br /&gt;JkShmFile /var/log/apache2/jk-runtime-status&lt;br /&gt;JkLogLevel    info&lt;/pre&gt;&lt;p&gt;You can also add JkMount instructions here, but I found that I needed to put them in each VirtualHost file in &lt;code&gt;/etc/apache2/sites-available/&lt;/code&gt;. Eg in my &lt;code&gt; default&lt;/code&gt; VirtualHost file I added:&lt;/p&gt;&lt;pre&gt;JkMount /*.jsp ajp13_worker&lt;br /&gt;JkMount /manager ajp13_worker&lt;br /&gt;JkMount /manager/* ajp13_worker&lt;br /&gt;JkMount /host-manager ajp13_worker&lt;br /&gt;JkMount /host-manager/* ajp13_worker&lt;/pre&gt;&lt;p&gt;An alternative is to define JkMount commands in &lt;code&gt;jk.load&lt;/code&gt; and add a JkMountCopy command to copy these settings to all virtual hosts.&lt;/p&gt;&lt;p&gt;You now need to edit the jk worker file &lt;code&gt;/etc/libapache2-mod-jk/workers.properties&lt;/code&gt;. Make sure it has code as follows, ie to tell jk to use the Tomcat connection we defined earlier:&lt;/p&gt;&lt;pre&gt;worker.list=ajp13_worker&lt;br /&gt;worker.ajp13_worker.port=8009&lt;br /&gt;worker.ajp13_worker.host=localhost&lt;br /&gt;worker.ajp13_worker.type=ajp13&lt;/pre&gt;&lt;p&gt;Restart apache.&lt;/p&gt;&lt;p&gt;I was now able to access the Tomcat manager here: http://www.example.com/manager/html.&lt;/p&gt;&lt;p&gt;Edit your other VirtualHost files to expose any other apps that you need.&lt;/p&gt;&lt;p&gt;You'll find the Apache and jk logs in here: &lt;code&gt;/var/log/apache2/&lt;/code&gt;. As above, keep an eye on these files and the disk space they use. &lt;/p&gt;&lt;p&gt;If you see "missing uri map" in log file &lt;code&gt;mod_jk.log&lt;/code&gt; then you need to define JkMount in your VirtualHost.&lt;/p&gt;&lt;p&gt;As a final task, if you wish, disable the Tomcat 8080 port by editing &lt;code&gt;/etc/tomcat6/server.xml&lt;/code&gt; and then restart Tomcat.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-2765377585418045764?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/2765377585418045764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=2765377585418045764' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/2765377585418045764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/2765377585418045764'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/11/installing-tomcat-onto-ubuntu-apache2.html' title='Installing Tomcat onto Ubuntu Apache2'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-4841039057182551464</id><published>2010-09-21T17:29:00.008+01:00</published><updated>2010-11-08T17:05:19.375Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Windows'/><title type='text'>Windows 7 system drive letters</title><content type='html'>&lt;p&gt;I've previously blogged about &lt;a href="http://chriscant.phdcc.com/2010/01/wandering-drive-letters-in-windows.html"&gt;Wandering drive letters in Windows 7&lt;/a&gt;.  I've now a little more hard-won knowledge of what's going on.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Be very careful if you resize a Windows 7 system partition when the partition letter is not C.  In fact, don't do it!&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I am revisiting this issue because I ran into a problem.  Our family shared laptop came with Vista.  However this started playing up showing a DOS box regularly and Windows Update wouldn't install a particular patch.  So I decided to install Windows 7 on drive D.  This worked fine with W7 running with its system drive as D:.&lt;/p&gt;&lt;p&gt;Recently, drive D: started getting full so I decided to resize the partitions with the great &lt;a href="http://www.partitionwizard.com/"&gt;Partition Wizard&lt;/a&gt;.  This seemed to go OK.  However, Windows 7 started playing up very badly, showing DwmHintDxUpdate error messages and worse.  I eventually tracked the problem down to the fact that Windows 7 had decided that drive D: would now be called drive C: which is a serious problem as Windows and other software will store many full file locations (in the registry and elsewhere).  If W7 thinks a file should be on drive D when in fact it is on drive C, then there's going to be serious problems.  It's amazing that it booted at all.&lt;/p&gt;&lt;p&gt;The post by MarcusOS7 &lt;a href="http://social.technet.microsoft.com/Forums/en-US/w7itproinstall/thread/47bcc58e-9792-409a-889f-796a07746a5e"&gt;here&lt;/a&gt; explained what was going on, ie that Windows 7 tries to rename its system partition to drive C if the drive is resized, ie when the drive identifier changes.  &lt;strong&gt;This is a serious flaw in Windows 7 and must be fixed.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;I had hoped that with this information, I would be able to mend my W7 system.  Even though I could just run regedit, I did not know what magic to do to change the system drive letter.&lt;/p&gt;&lt;p&gt;Eventually I decided to reinstall Windows 7.  I saved the data over to an external USB drive.  For this installation I wanted to be sure that W7 would install itself on drive C.  Previously I must have done the installation when running the Vista system which somehow prevented W7 from using the C drive.  This time, I was careful to do the install after booting into the Windows DVD.  This trick ensured that W7 marked its install partition as drive C (even though it was not on the first partition).&lt;/p&gt;&lt;p&gt;In fact: before doing this, I re-partitioned the system so I had 3 partitions in the laptop, one each for Vista and W7, and one as a data drive.  This process also helped me clear the previous W7 installation which was quite stubborn to remove, permission-wise.  I eventually had to clear the partition by reformatting the drive.&lt;/p&gt;&lt;p&gt;Back in the new W7 system, I was able to restore the desired user data from the backup.  And then I had to set up W7 again from scratch for each user, and installing all my development tools.  As usual one of my first tasks is to turn off various system sounds, particularly the annoying Start Navigation click...&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-4841039057182551464?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/4841039057182551464/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=4841039057182551464' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/4841039057182551464'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/4841039057182551464'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/09/windows-7-system-drive-letters.html' title='Windows 7 system drive letters'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-6068217429513897780</id><published>2010-08-25T22:21:00.002+01:00</published><updated>2010-08-25T22:35:49.162+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Drupal'/><title type='text'>Drupal staging site security</title><content type='html'>Suppose you have a Drupal staging site where you are preparing to go live or testing new features. You could install this on a separate domain that you have. Here's how to redirect casual users to your live site, while giving those in the know easy entry.&lt;br /&gt;&lt;br /&gt;The crucial trick is to use a Session variable to indicate an authorised user. All Drupal access is via the root index.php file (except use of static files). index.php is amended to redirect users who do not have the session variable set correctly. Another secret file eg password.php, is used to let you get into the site by setting the session variable.&lt;br /&gt;&lt;br /&gt;In the following example, www.example.com is your live domain and www.example.info is the staging server.  The following code is on the staging server.&lt;br /&gt;&lt;br /&gt;In index.php add this code after the line that contains drupal_bootstrap...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;if( $_SESSION['password']!='asecret')&lt;br /&gt;{&lt;br /&gt;  header('Location: http://www.example.com/');&lt;br /&gt;  exit;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Create a secret file in the root directory eg password.php with content like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;require_once './includes/bootstrap.inc';&lt;br /&gt;drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);&lt;br /&gt;&lt;br /&gt;if( $_SESSION['password']=='asecret')&lt;br /&gt;{&lt;br /&gt;  header('Location: http://www.example.info/');&lt;br /&gt;  die();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;$pwd = trim($_POST['pwd']);&lt;br /&gt;if( get_magic_quotes_gpc())&lt;br /&gt;{&lt;br /&gt; $pwd = stripslashes($pwd);&lt;br /&gt;}&lt;br /&gt;if( $pwd=='asecret')&lt;br /&gt;{&lt;br /&gt;  $_SESSION['password'] = $pwd;&lt;br /&gt;  header('Location: http://www.example.info/');&lt;br /&gt;  die();&lt;br /&gt;}&lt;br /&gt;?&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;html&amp;gt;&lt;br /&gt;&amp;lt;body&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;form method="post"&amp;gt;&lt;br /&gt;&lt;br /&gt;Security:&lt;br /&gt;&amp;lt;input type="text" name="pwd" /&amp;gt;&lt;br /&gt;&amp;lt;input type="submit" value="Go" /&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-6068217429513897780?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/6068217429513897780/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=6068217429513897780' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/6068217429513897780'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/6068217429513897780'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/08/drupal-staging-site-security.html' title='Drupal staging site security'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-8165235773973049577</id><published>2010-04-23T13:59:00.002+01:00</published><updated>2010-04-23T14:09:40.473+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='maps'/><category scheme='http://www.blogger.com/atom/ns#' term='Google'/><category scheme='http://www.blogger.com/atom/ns#' term='mashup'/><title type='text'>Tool to find maps KML Lat/Lng</title><content type='html'>Use this tool if you want to find a Google Maps KML GLatLng location, eg to use within your own code: &lt;a href="http://www.phdcc.com/GoogleLatLng.htm"&gt;http://www.phdcc.com/GoogleLatLng.htm&lt;/a&gt;  Either drag the initial marker or enter a search term.  The latitude and longitude values are listed below, to 6 decimal places of accuracy.&lt;br /&gt;&lt;br /&gt;The map is centred on the UK, as this is where I will want to use it most.  It uses the Google AJAX Search API to work out UK postcodes well.  Look at the source to see how it works.&lt;br /&gt;&lt;br /&gt;This is based on the code by cmarshall at &lt;a href="http://www.webmasterworld.com/xml/3542700.htm"&gt;http://www.webmasterworld.com/xml/3542700.htm&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-8165235773973049577?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/8165235773973049577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=8165235773973049577' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8165235773973049577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8165235773973049577'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/04/tool-to-find-maps-kml-latlng.html' title='Tool to find maps KML Lat/Lng'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-2519841563173039500</id><published>2010-04-19T12:45:00.005+01:00</published><updated>2010-04-19T13:03:54.486+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>DNN5 User soft-delete issues</title><content type='html'>In DotNetNuke DNN5 when you delete a user that login is no longer removed from the system.  Instead they are soft-deleted, ie a new IsDeleted flag column is set in the UserPortals table. (Note that IsDeleted in the Users table is *not* set - is this ever set?)&lt;br /&gt;&lt;br /&gt;In DNN5, code that calls DotNetNuke.Entities.Users.UserController.GetUser() etc will return a UserInfo object even if the user is deleted.  Therefore you may have to check the UserInfo.IsDeleted property every time you get a user.&lt;br /&gt;&lt;br /&gt;I would have not have implemented it this way.  I'd keep the DNN4 functionality and have extra API calls to find deleted users.  I wonder: does the DNN core code always check IsDeleted now?&lt;br /&gt;&lt;br /&gt;Anyway, UserInfo.IsDeleted is not available in DNN4.  As I do not want different versions of my code for DNN4 and DNN5, I have written this isUserDeleted() static method that uses reflection to detect if the IsDeleted property is available and if so calls it.  It returns true if the UserInfo is null.  After that, for DNN4 it always returns false.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;public static bool isUserDeleted(UserInfo ui)&lt;br /&gt;{&lt;br /&gt; if (ui == null) return true;&lt;br /&gt; try          {&lt;br /&gt;  Type tUserInfo = ui.GetType();&lt;br /&gt;&lt;br /&gt;  PropertyInfo piIsDeleted = tUserInfo.GetProperty("IsDeleted");&lt;br /&gt;  if (piIsDeleted != null)&lt;br /&gt;  {&lt;br /&gt;   bool IsDeleted = (bool)piIsDeleted.GetValue(ui, null);&lt;br /&gt;   return IsDeleted;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; catch (Exception) { }&lt;br /&gt; return false;&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-2519841563173039500?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/2519841563173039500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=2519841563173039500' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/2519841563173039500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/2519841563173039500'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/04/dnn5-user-soft-delete-issues.html' title='DNN5 User soft-delete issues'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-8488635505539581864</id><published>2010-04-10T09:00:00.021+01:00</published><updated>2010-04-14T17:09:09.445+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gadget'/><category scheme='http://www.blogger.com/atom/ns#' term='Google'/><category scheme='http://www.blogger.com/atom/ns#' term='mashup'/><category scheme='http://www.blogger.com/atom/ns#' term='Freegle'/><title type='text'>Freegle Find a Group mashup</title><content type='html'>&lt;p&gt;This blog describes how I've used one JavaScript script to power several different incarnations of a Google Maps tool to help people Find a Freegle Group.  Usage instructions for the Find a Group tool are on the &lt;a href="http://wiki.ilovefreegle.org/Find_a_Group_maps"&gt;Freegle wiki&lt;/a&gt;.  You can also see the Find a Group gadger lower down on this page on the right.&lt;/p&gt;&lt;p&gt;&lt;b&gt;This is primarily a technical article - for usage instructions, see the above link.&lt;/b&gt;&lt;p&gt;&lt;img src="http://maps.iLoveFreegle.org/freegle_gadget_screenshot_280x246.png" alt="Freegle Find a Group screenshot" title="Freegle Find a Group screenshot" style="float:right;padding:2px;border:1px;" /&gt;&lt;a href="http://www.ilovefreegle.org/"&gt;Freegle&lt;/a&gt; is a network of local re-use groups in the UK.  Group members give away and receive any unwanted items for free.  This helps conserve the world's resources, clears clutter and helps others.&lt;/p&gt;&lt;p&gt;The Find a Group tool shows a UK map with pinpoint markers for each Freegle group.  You can move around and zoom in as normal - clicking on a marker shows an information bubble - clicking on the link in the bubble takes you to the group web site.&lt;/p&gt;&lt;p&gt;Find a Group also has a search box.  The tool uses Google to search for that location in the UK.  If found, the tool zooms to that location and adds a marker - you are invited to look for the nearest Freegle group marker.  The nearest groups are listed below&lt;/p&gt;&lt;h2&gt;Source data&lt;/h2&gt;&lt;p&gt;The green group markers come from &lt;a href="http://maps.google.co.uk/maps/ms?hl=en&amp;ie=UTF8&amp;oe=UTF8&amp;t=p&amp;msa=0&amp;msid=104032218577461097800.000478fbc5161363404bd&amp;output=kml"&gt;this KML file&lt;/a&gt; - maintained by another Freegle member.  This is the latest data so the tool should always be up to date.&lt;/p&gt;&lt;p&gt;Actually, that's not the whole truth.  The tool needs to load the KML file into memory so it can search for groups.  Loading the above Google Maps KML link doesn't work because of cross-site scripting security (as I understand it).  Therefore the code loads a separate snapshot of the KML file from a URL that it can access.&lt;/p&gt;&lt;p&gt;There's a plan to resolve this issue by having the primary KML file on the iLoveFreegle.org servers.  That way, my code would only need to reference one file.  And the plan is to have this file generated directly from the main Freegle groups database, so it is always up to date.  (Currently the Google Maps KML file has to be updated by hand.)&lt;/p&gt;&lt;h2&gt;Mashup variants&lt;/h2&gt; &lt;h3&gt;(P) Full web page&lt;/h3&gt;&lt;p&gt;The Find a Group tool occupies a full web page here: &lt;a href="http://maps.ilovefreegle.org/"&gt;maps.ilovefreegle.org&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;(M) Google Mapplet&lt;/h3&gt;&lt;p&gt;The Find a Group tool appears in the Google mapplets directory &lt;a href="http://maps.google.co.uk/gadgets/directory?synd=mpl&amp;hl=en&amp;gl=en&amp;url=http%3A%2F%2Fmaps.ilovefreegle.org%2Ffreegle_mapplet.xml"&gt;here&lt;/a&gt;.  When added to My Maps it shows filling the browser window.&lt;/p&gt;&lt;h3&gt;(I) Iframe&lt;/h3&gt;&lt;p&gt;The Iframe version of the Find a Group tool is designed to fit into a small window within another page.  The &lt;a href="http://wiki.ilovefreegle.org/Find_a_Group_maps#IFrame"&gt;usage instructions&lt;/a&gt; describe the various configuration parameters.&lt;/p&gt;&lt;p&gt;You can see the iframe in action at the &lt;a href="http://groups.yahoo.com/group/PenrithEdenFreegle/"&gt;Penrith and Eden District Freegle group&lt;/a&gt; and its associated &lt;a href="http://www.penrithedenfreegle.org.uk/"&gt;helper web site&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;(G) Google gadget&lt;/h3&gt;&lt;p&gt;The Find a Group tool is packaged as a Google Gadget, &lt;a href="http://maps.ilovefreegle.org/freegle_gadget.xml"&gt;defined here&lt;/a&gt;.  You can therefore add it to iGoogle, add it to a blog, and get the code to put on a web site.  The gadget has similar options to the iframe.&lt;/p&gt;&lt;p&gt;The gadget has a Content type of url, with the main gadget code &lt;a href="http://maps.iLoveFreegle.org/freegle_gadget.php"&gt;here&lt;/a&gt;.  The gadget options are passed as URL QueryString parameters.&lt;/p&gt;&lt;h3&gt;(F) Facebook application&lt;/h3&gt;&lt;p&gt;The Find a Group tool is being developed as a simple Facebook iframe application.&lt;/p&gt;&lt;h2&gt;Common Page Layout&lt;/h2&gt;&lt;p&gt;Each of the above tools has HTML which has the same page elements:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Map div called "map_canvas"&lt;/li&gt;&lt;li&gt;Noscript block&lt;/li&gt;&lt;li&gt;Div called "jsBlock" that wraps the search form&lt;/li&gt;&lt;li&gt;Input text box called "search"&lt;/li&gt;&lt;li&gt;Submit button that has an onclick handler "return doSearch();" that always returns false&lt;/li&gt;&lt;li&gt;Information div called "idInfo"&lt;/li&gt;&lt;li&gt;Debug div called "idDebug" that shows version info by default&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Accessibility&lt;/h2&gt;&lt;p&gt;The tool has a noscripts block that has a link to the main site groups list - this is shown if the browser has no JavaScript or it is turned off.  However Chrome doesn't show this text if JavaScript is turned off - sounds like a bug to me.&lt;/p&gt;&lt;p&gt;The "jsBlock" search form div is not displayed by default; the JavaScript makes this block visible.  This avoids confusion if JavaScript is not available.&lt;/p&gt;&lt;p&gt;The search text box has an accesskey of "4" defined.  For the larger variants, a label is defined for this text box.&lt;/p&gt;&lt;p&gt;The tool HTML is hopefully valid XHTML and CSS.&lt;/p&gt;&lt;h2&gt;API and api keys&lt;/h2&gt;&lt;p&gt;The Google Maps/Mapplets API is used in the JavaScript.  There are various differences depending on whether it is a mapplet or not.  Therefore one of the JavaScript initialise() parameters is a mapplet boolean; this is stored in a global for later use throughout the code.&lt;/p&gt;&lt;p&gt;To use the Google Maps API, each site needs an API key.  All these tools run on the maps.iLoveFreegle.org domain so the iLoveFreegle.org API key is used.  The mapplet code does not need an API key.&lt;/p&gt;&lt;h2&gt;JavaScript structure&lt;/h2&gt;&lt;p&gt;The JavaScript is pretty straightforward procedural code, with some globals, an &lt;i&gt;initialise()&lt;/i&gt; function and a function &lt;i&gt;doSearch()&lt;/i&gt; that is called when a user does a search.  The JavaScript code is here: &lt;a href="http://maps.ilovefreegle.org/freegle_maps.js"&gt;http://maps.ilovefreegle.org/freegle_maps.js&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;initialise() function&lt;/h3&gt;&lt;p&gt;This takes parameter specifying whether big map controls are wanted, and the mapplet boolean.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If not a mapplet, check that the browser is Google Maps compatible&lt;/li&gt;&lt;li&gt;Get references to the "idInfo" and "idDebug" page elements - add the js version to idDebug&lt;/li&gt;&lt;li&gt;If the "jsBlock" page element is present, change its display style to block to make it show.&lt;/li&gt;&lt;li&gt;Get the map object and set the map height if it has been specified in a parameter.&lt;/li&gt;&lt;li&gt;Get the z SearchZoom integer parameter for later use&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Set the map to be the approx centre of the UK at a suitable scale&lt;/li&gt;&lt;li&gt;Use GGeoXml to load the primary KML file and add it as an overlay - the code cannot access the contents of the overlay, so...&lt;/li&gt;&lt;li&gt;Initiate a separate load of the (secondary) KML for processing, either using _IG_FetchXmlContent() or GDownloadUrl()&lt;/li&gt;&lt;li&gt;Finally set the focus to the search box if required&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;processKML() function&lt;/h3&gt;&lt;p&gt;This parses the received KML XML file and stores info about each Group placemark object in the Groups array.&lt;/p&gt;&lt;p&gt;Note that the KML file stores each coordinate the wrong way round, ie longitude, latitude then height.  The GLatLng.fromUrlValue() function needs just the first two values but reversed.&lt;/p&gt;&lt;p&gt;Once the KML is loaded, it sees if a search has been requested and does it.&lt;/p&gt;&lt;h3&gt;doSearch() function&lt;/h3&gt;&lt;p&gt;The search function does a Google GClientGeocoder search and zooms in to show the result.  The search also lists the nearest groups, calculated using GLatLng.distanceFrom().&lt;/p&gt;&lt;p&gt;doSearch() uses showAddressResult(), SearchGroups() and createMarker().&lt;/p&gt;&lt;h3&gt;Helper functions&lt;/h3&gt;&lt;p&gt;String.prototype.trim, PageQuery, queryString() and isInteger() were obtained from code on the Internet.  I added queryString2() helper function.&lt;/p&gt;&lt;p&gt;I have updated PageQuery to cope with URL parameters with key=value pairs where "=value" is missing.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-8488635505539581864?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/8488635505539581864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=8488635505539581864' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8488635505539581864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8488635505539581864'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/04/freegle-find-group-mashup.html' title='Freegle Find a Group mashup'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-7869338740635118680</id><published>2010-03-19T19:41:00.007Z</published><updated>2010-03-19T19:49:54.445Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='Vista'/><title type='text'>800700C1 KB977165 and verclsid</title><content type='html'>Our Vista laptop has developed 3 problems recently, which are probably all related.  Can anyone help?&lt;br /&gt;&lt;br /&gt;Windows Update keeps on trying to install KB977165, sometimes successfully, but it keeps re-appearing.&lt;br /&gt;&lt;br /&gt;Clicking on Check for Updates now fails with error 800700C1.&lt;br /&gt;&lt;br /&gt;It also often shows an error with a DOS box title C:\Windows\system32\verclsid.exe and a message 16 bit MS-DOS Subsystem saying The NTVDM CPU has encountered an illegal instruction.&lt;br /&gt;&lt;br /&gt;We did have some software for 2 HP printers installed.  I've uninstalled one of them as this was suggested as being a problem.&lt;br /&gt;&lt;br /&gt;Any other ideas pls?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_hkkBsHR0r5s/S6PU1MtMI0I/AAAAAAAAAMY/-yJpE2Nv8u4/s1600-h/WindowsVistaKB977165.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 200px; height: 88px;" src="http://2.bp.blogspot.com/_hkkBsHR0r5s/S6PU1MtMI0I/AAAAAAAAAMY/-yJpE2Nv8u4/s200/WindowsVistaKB977165.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5450433984457352002" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_hkkBsHR0r5s/S6PU1dJGUSI/AAAAAAAAAMg/O_Fg7nUaaeA/s1600-h/WindowsUpdate800700C1.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 200px; height: 112px;" src="http://1.bp.blogspot.com/_hkkBsHR0r5s/S6PU1dJGUSI/AAAAAAAAAMg/O_Fg7nUaaeA/s200/WindowsUpdate800700C1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5450433988869378338" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_hkkBsHR0r5s/S6PU1j8XNLI/AAAAAAAAAMo/tcXZ4sN0dHY/s1600-h/NTVDMerror2.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 200px; height: 102px;" src="http://3.bp.blogspot.com/_hkkBsHR0r5s/S6PU1j8XNLI/AAAAAAAAAMo/tcXZ4sN0dHY/s200/NTVDMerror2.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5450433990695007410" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-7869338740635118680?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/7869338740635118680/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=7869338740635118680' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/7869338740635118680'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/7869338740635118680'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/03/800700c1-kb977165-and-verclsid.html' title='800700C1 KB977165 and verclsid'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_hkkBsHR0r5s/S6PU1MtMI0I/AAAAAAAAAMY/-yJpE2Nv8u4/s72-c/WindowsVistaKB977165.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-988104417504935403</id><published>2010-03-09T10:42:00.003Z</published><updated>2010-03-10T20:49:44.769Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>Crystaltech to 3essentials DNN copy</title><content type='html'>&lt;p&gt;PHDCC Director John Cant describes the process of transferring a DNN site from one shared host to another, at this stage simply as a backup using a different/private domain.&lt;/p&gt;&lt;p&gt;The site, ourdnnsite.com is being copied from Newtek (Crystaltech) hosting to 3Essentials with domain ourdnnsite.info. Both support services are very helpful but it currently seems that 3essentials runs faster than Crystaltech, probably because of a faster database server.&lt;/p&gt;&lt;p&gt;The existing site runs only one portal but this contains many users and many files in /Portals/0/. Maintaining the UserIDs is crucial as much custom information in the database is keyed off this value.&lt;/p&gt;&lt;p&gt;&lt;a href="http://knowledge.3essentials.com/web-hosting/article/369/What-ASP.NET-versions-1.1-2.0-3.5-does-3Essentials-support-and-how-do-I-enable-change-versions.html"&gt;Enable ASP.NET 3.5 at the 3essentials site.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;First, place a holding page at ourdnnsite.info, eg index.html, that will be shown instead of Default.aspx for people accessing the root domain URL.&lt;/p&gt;&lt;p&gt;Make sure to truncate transaction log prior to the upgrade, clear the eventlog and maybe even sitelog and search tables, if those are exaggeratingly large.&lt;/p&gt;&lt;p&gt;The 3essentials site currently runs SQL Server 2005/2003. They need a .BAK backup file so that the database copy can be restored correctly.&lt;/p&gt;&lt;p&gt;Do a database backup on the Crystaltech site and ask for the entire site to be zipped, to get these files:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;ourdnnsitedb_1002050754.bak&lt;/li&gt;&lt;li&gt;ourdnnsite.zip&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;p&gt;Load these to the 3essentials site ourdnnsite.info /private/&lt;/p&gt;&lt;p&gt;Submit a 3essentials site support request, asking for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the database to be restored from /private/ourdnnsitedb_1002050754.bak&lt;/li&gt;&lt;li&gt;the site files to be unzipped from /private/ourdnnsite.zip&lt;/li&gt;&lt;li&gt;the site file permissions to be set correctly:&lt;br /&gt;"I have corrected this issue, this involves giving the httpdocs directory IWPD User - Modify permissions which you are not able to do via Plesk."&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Check the Web.Config file at the 3essentials site:&lt;/p&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;set the new database connection string twice&lt;/li&gt;&lt;li&gt;check the ObjectQualifier&lt;/li&gt;&lt;li&gt;set CustomErrors to 'Off' to see the errors&lt;/li&gt;&lt;li&gt;ensure the machinekey values are the same as the original values from the Crystaltech site&lt;/li&gt;&lt;li&gt;set AutoUpgrade off&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;On the database:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Update the portal alias, eg:&lt;br /&gt;&lt;code&gt;UPDATE [DNN_PortalAlias] SET&lt;br /&gt;HTTPAlias='www.ourdnnsite.info' WHERE PortalAliasID=0&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Access the site by visiting Default.aspx&lt;/p&gt;&lt;p&gt;&lt;br /&gt;If any errors are visible, keep deleting the sections of Web.Config that cause errors.&lt;/p&gt;&lt;p&gt;If you cannot log in, use Chris' technique, &lt;a href="http://knowledge.3essentials.com/web-hosting/article/376/Reset-DNN-host-password.html"&gt;described here&lt;/a&gt;. To access the DNN database, log in using myLittleAdmin here: &lt;a href="http://db1.3essentials.com/mla/"&gt;http://db1.3essentials.com/mla/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;To prevent unauthorised access to the ourdnnsite.info site, add to the Default.aspx file a check that a cookie is set. Provide a means of setting the cookie using another private page.&lt;/p&gt;&lt;p&gt;There are a couple of other additions that we usually make:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;As well as creating an app_offline.htm file, we use the Cookie technique in Default.aspx to ensure that the site is offline during maintenance, redirecting to an offline page.&lt;/li&gt;&lt;li&gt;In the skin, we warn people who are online that the site is about to go down; this also helps us see if anyone is online now.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;More details on these Default.aspx techniques later if you wish.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-988104417504935403?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/988104417504935403/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=988104417504935403' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/988104417504935403'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/988104417504935403'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/03/crystaltech-to-3essentials-dnn-copy.html' title='Crystaltech to 3essentials DNN copy'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-9156108082597503727</id><published>2010-02-24T12:02:00.005Z</published><updated>2010-02-24T12:17:41.425Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET ASP.NET'/><title type='text'>.NET app using Jet on x64</title><content type='html'>If you are trying to use an Access MDB file using the Microsoft Jet OleDb driver in .NET or ASP.NET, you must set your platform target to x86 (32 bit).&lt;br /&gt;&lt;br /&gt;If you are running on a 64 bit system and your assembly target is Any CPU or x64 then you will see this exception:&lt;br /&gt;&lt;em&gt;The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine. &lt;/em&gt;&lt;br /&gt;&lt;br /&gt;The Microsoft ACE.OLEDB driver for MDB and ACCDB files works OK whichever platform is targeted.  However this driver is not installed by default on user computers unlike the Jet driver.&lt;br /&gt;&lt;br /&gt;This fix can be used to make existing code work with Jet/MDB, eg the Cassini server.&lt;br /&gt;&lt;br /&gt;Useful blog links:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.lostechies.com/blogs/gabrielschenker/archive/2009/10/21/force-net-application-to-run-in-32bit-process-on-64bit-os.aspx"&gt; Force .NET application to run in 32bit process on 64bit OS&lt;/a&gt; by Gabriel Schenker&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.hanselman.com/blog/BackToBasics32bitAnd64bitConfusionAroundX86AndX64AndTheNETFrameworkAndCLR.aspx"&gt;Back to Basics: 32-bit and 64-bit confusion around x86 and x64 and the .NET Framework and CLR&lt;/a&gt; by Scott Hanselman&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-9156108082597503727?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/9156108082597503727/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=9156108082597503727' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/9156108082597503727'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/9156108082597503727'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/02/net-app-using-jet-on-x64.html' title='.NET app using Jet on x64'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-4581709963362320971</id><published>2010-02-22T14:04:00.006Z</published><updated>2010-02-22T15:07:29.799Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='.net string UTF8'/><title type='text'>System.String hidden UTF8 BOM</title><content type='html'>In .NET, a string (System.String) can contain an initial UTF-8 Byte Order Mark (BOM) which might not be seen in ordinary processing but is present when converted to a character array or into an encoding byte array.&lt;br /&gt;&lt;br /&gt;For example, a text file might be saved in UTF8 format with UTF-8 Byte Order Mark bytes at the start, ie 0xEF 0xBB 0xBF.  You might receive this file in ASP.NET using a FileUpload control, or read it directly in a Forms .NET app in C#:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;byte[] FileBytes = File.ReadAllBytes(path);&lt;br /&gt;string content = Encoding.UTF8.GetString(FileBytes);&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If the file contains these 7 bytes (in hex) EF BB BF 44 65 61 72 then content will superficially contain the single word "Dear", eg as seen in the debugger, and content.StartsWith("Dear") will return true.&lt;br /&gt;&lt;br /&gt;However, content.Length is 5 and content.ToCharArray() will return an array with 5 elements, the first being set to 0xFEFF.  Similarly, Encoding.UTF8.GetBytes(content) will return the same 7 bytes as was used in the first place.&lt;br /&gt;&lt;br /&gt;(Note that that has nothing to do with the encoderShouldEmitUTF8Identifier optional parameter for the UTF8Encoding constructor.)&lt;br /&gt;&lt;br /&gt;As this hidden extra character can be misleading, I have written the following snippet that detects the presence of the UTF8 Byte Order Mark preamble and ignores it if present:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;byte[] FileBytes = File.ReadAllBytes(path);&lt;br /&gt;int StartPoint = 0;&lt;br /&gt;int Count = FileBytes.Length;&lt;br /&gt;if ( Count&gt;= 3 &amp;&amp; FileBytes[0] == 0xEF &amp;&amp; FileBytes[1] == 0xBB &amp;&amp; FileBytes[2] == 0xBF)&lt;br /&gt;{&lt;br /&gt; StartPoint += 3;&lt;br /&gt; Count -= 3;&lt;br /&gt;}&lt;br /&gt;content = Encoding.UTF8.GetString(FileBytes, StartPoint, Count);&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;PS  The code could not doubt be improved using Encoding.GetPreamble()&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-4581709963362320971?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/4581709963362320971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=4581709963362320971' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/4581709963362320971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/4581709963362320971'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/02/systemstring-hidden-utf8-bom.html' title='System.String hidden UTF8 BOM'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-8206748054448756984</id><published>2010-02-07T12:59:00.006Z</published><updated>2010-02-07T14:01:08.727Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><title type='text'>Clearing nested CSS floats</title><content type='html'>Here's a XHTML/CSS technique to ensure you only clear the desired floats and get the right background areas for your DIV.&lt;br /&gt;&lt;br /&gt;The code examples are here: &lt;a href="http://www.phdcc.com/CSS_ClearingFloats.htm"&gt;http://www.phdcc.com/CSS_ClearingFloats.htm&lt;/a&gt; - look at the source code for full details.&lt;br /&gt;&lt;br /&gt;My starting point is to have a DIV set with floats left and right.  Within the middle unfloated DIV, I have another DIV set with floats and right.  I want to clear just the middle floats, not the outer ones.&lt;br /&gt;&lt;br /&gt;The crucial trick is to add CSS style 'overflow:hidden' to create a Block Formatting Context.  Any CSS style 'clear:both' then only applies to the 'nearest'/current Block Formatting Context.&lt;br /&gt;&lt;br /&gt;An associated problem was the fact that the background of the middle unfloated DIV goes wide to the border of the current Block Formatting Context.  Adding CSS style 'overflow:hidden' to create a new context constrains the background to the expected area.&lt;br /&gt;&lt;br /&gt;Using 'overflow:hidden' feels slightly naff: shouldn't there be an explicit CSS style to define a Block Formatting Context, instead of using something that does this as a side effect?  Also, you may be concerned about what overflow is being hidden - as long as you have the default width:auto and/or height:auto set, you will not be hiding anything.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-8206748054448756984?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/8206748054448756984/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=8206748054448756984' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8206748054448756984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8206748054448756984'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/02/clearing-nested-css-floats.html' title='Clearing nested CSS floats'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-1593332343396110508</id><published>2010-02-05T15:01:00.014Z</published><updated>2010-02-05T16:01:00.066Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='accessibility'/><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>Adding Access key support to DotNetNuke</title><content type='html'>This page explains how to add "access key" support to a DNN5 web site.&lt;br /&gt;Access keys provide keyboard short-cuts to improve web accessibility, as described here - which also lists the UK government recommendations:&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Access_key" title="Access key keyboard short-cuts"&gt;http://en.wikipedia.org/wiki/Access_key&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I have implemented access keys using the accesskey attribute rather than the&lt;br /&gt;&lt;a href="http://www.w3.org/TR/2005/WD-xhtml2-20050527/mod-role.html#s_rolemodule"&gt;Role Access Model&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;How it works for a user&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;For standard users, each browser uses different modifier keys and acts minorly differently when pressed.  Consider accesskey '1'.  In Windows Internet Explorer, pressing Alt+1 selects the corresponding link - you must press Enter to go to that link.  In Firefox, pressing Alt+Shift+1 goes to the associated link immediately.&lt;br /&gt;&lt;br /&gt;These accesskey shortcuts take precedence over standard Window commands, so if you define an accesskey 'f' it will be acted on rather than show the File menu in Windows.&lt;br /&gt;&lt;br /&gt;In Internet Explorer, the link is not selected if the link has a style of display:none.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Search accesskey&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Most of the access keys can be defined in your DNN skin using extra HTML that is not normally seen.  However, accesskey 4 takes you straight to the Search text box.  To make this happen, I had to amend &lt;code&gt;admin/Skins/search.ascx&lt;/code&gt; to add AccessKey="4" to the txtSearchNew asp:TextBox.  &lt;br /&gt;&lt;br /&gt;While there, I took the opportunity to add a label for this box, as recommended for all fields to improve accessibility.  This label is present but not normally displayed:&lt;br /&gt;&lt;code&gt;&amp;lt;asp:Label AssociatedControlID="txtSearchNew" style="display:none;" runat="server"&amp;gt;Search:&amp;lt;/asp:Label&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Main accesskeys&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;The main site access keys can be defined using code like this at the top of your skin, which I adapted from another web site:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&amp;lt;ul id="skips"&amp;gt;&lt;br /&gt;&amp;lt;li&amp;gt;&amp;lt;a href="#content" accesskey="s"&amp;gt;Skip to page content, accesskey=s&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;&amp;lt;li&amp;gt;&amp;lt;asp:HyperLink ID="SkipToHome" NavigateUrl="~/" AccessKey="1" runat="server"&amp;gt;Skip to Home page, accesskey=1&amp;lt;/asp:HyperLink&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;CSS (below) can then be used to make sure that this information is not normally seen, but each link become visible when it has the focus or is active.  The list-style is set to none to avoid bullet points etc.  When not in focus, the links have width and height 0; when in focus these are set to auto.  Tweak the other settings as you wish.&lt;br /&gt;&lt;br /&gt;Finally, don't forget to provide an anchor within your skin for the skip to navigation link:&lt;br /&gt;&amp;lt;a id="content" name="content"&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;CSS code:&lt;/strong&gt;&lt;br /&gt;&lt;code&gt;ul#skips { list-style: none; }&lt;br /&gt;#skips li { list-style: none; display: inline; }&lt;br /&gt;#skips a&lt;br /&gt;{&lt;br /&gt; color: white; width: 0; height: 0; overflow: hidden; z-index: 1000;&lt;br /&gt; position: absolute; top: 15px; left: 100px;&lt;br /&gt;}&lt;br /&gt;#skips a:active, #skips a:focus&lt;br /&gt;{&lt;br /&gt; color: red; border: 1px solid red;&lt;br /&gt; width: auto; height: auto;&lt;br /&gt; display: block; overflow: visible;&lt;br /&gt; padding: 3px;&lt;br /&gt;}&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-1593332343396110508?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/1593332343396110508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=1593332343396110508' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1593332343396110508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1593332343396110508'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/02/adding-access-key-support-to-dotnetnuke.html' title='Adding Access key support to DotNetNuke'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-3346076260305952308</id><published>2010-02-01T15:10:00.015Z</published><updated>2010-02-01T15:54:19.034Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>DNN local install with SQL Server</title><content type='html'>&lt;div&gt;The post summarises the steps required to set up DotNetNuke 5 (DNN5) locally on Windows using the full Microsoft SQL Server using SQL Server authentication, ie not the Express version. Getting the Logins and Users right is the crucial trick that I want to remember.&lt;br /&gt;&lt;br /&gt;Click on each image to see it larger.&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;In Microsoft SQL Server Management Studio, connect to your local server. Right-click on Databases on the left. Select "New database". In the following screen, enter a database name, eg "DNN522pen" and click OK without changing anything else.&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bviYI8U5I/AAAAAAAAALI/Sv40At0fMOw/s1600-h/LocalServer1.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 180px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293374343631762" border="0" alt="" src="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bviYI8U5I/AAAAAAAAALI/Sv40At0fMOw/s200/LocalServer1.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Now, find the server Security and right-click on Logins and select "New Login...":&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_hkkBsHR0r5s/S2bzDR67gqI/AAAAAAAAAMI/SfsDLTUGSdk/s1600-h/LocalServer1half.png"&gt;&lt;img style="WIDTH: 197px; HEIGHT: 200px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433297238144811682" border="0" alt="" src="http://1.bp.blogspot.com/_hkkBsHR0r5s/S2bzDR67gqI/AAAAAAAAAMI/SfsDLTUGSdk/s200/LocalServer1half.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Enter a login name such as "dnn522pen", select "SQL Server Authentication" and enter the password.  Untick "Enforce password policy" if you wish.  Do not set the Default database.  Click OK.&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_hkkBsHR0r5s/S2bvitHhJfI/AAAAAAAAALQ/tk3Us7vxkH4/s1600-h/LocalServer2.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 180px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293379974800882" border="0" alt="" src="http://4.bp.blogspot.com/_hkkBsHR0r5s/S2bvitHhJfI/AAAAAAAAALQ/tk3Us7vxkH4/s200/LocalServer2.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Open up your database tree on the left: open "dnn522pen" then Security then Users.  Right-click and select "New user...":&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvi_r6loI/AAAAAAAAALY/aV_P7_fGMM0/s1600-h/LocalServer3.png"&gt;&lt;img style="WIDTH: 138px; HEIGHT: 200px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293384959301250" border="0" alt="" src="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvi_r6loI/AAAAAAAAALY/aV_P7_fGMM0/s200/LocalServer3.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;In the new user dialog, enter a User name such as "dnn522pen" and the Login name you used before eg "dnn522pen".  Tick "db_owner" twice below and click OK:&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvjDJpv_I/AAAAAAAAALg/FrHIYiN2ItM/s1600-h/LocalServer4.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 180px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293385889333234" border="0" alt="" src="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvjDJpv_I/AAAAAAAAALg/FrHIYiN2ItM/s200/LocalServer4.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Next, create a new folder on disk somewhere, eg in directory "D:\dnn522pen\".  Unzip the DotNetNuke community installation file in there &lt;code&gt;DotNetNuke_Community_05.02.02_Install.zip&lt;/code&gt;.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Open up Internet Information Services (IIS 7) Manager.  Expand the menu on the left to open Sites and Default Web Site.  Click on "Add Application..." on the right.&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvjdZQKqI/AAAAAAAAALo/hI0HeZPqQak/s1600-h/LocalServer5.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 200px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293392934087330" border="0" alt="" src="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvjdZQKqI/AAAAAAAAALo/hI0HeZPqQak/s200/LocalServer5.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Enter a suitable alias and physical path, eg "dnn522pen" and "D:\dnn522pen\":&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_hkkBsHR0r5s/S2b2agJS6fI/AAAAAAAAAMQ/_3g6oKJeb3k/s1600-h/LocalServer5half.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 200px; height: 138px;" src="http://1.bp.blogspot.com/_hkkBsHR0r5s/S2b2agJS6fI/AAAAAAAAAMQ/_3g6oKJeb3k/s200/LocalServer5half.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5433300935635036658" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;In IIS Manager, select the new alias on the left, select Content View and click "Browse" to start the DNN installation:&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvwq8vsSI/AAAAAAAAALw/kKecbggrhiM/s1600-h/LocalServer6.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 200px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293619910914338" border="0" alt="" src="http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bvwq8vsSI/AAAAAAAAALw/kKecbggrhiM/s200/LocalServer6.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;After a delay you should see the first DNN install screen.  Choose custom:&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_hkkBsHR0r5s/S2bvw5rZzgI/AAAAAAAAAL4/kNvsBcVm0co/s1600-h/LocalServer7.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 168px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293623864708610" border="0" alt="" src="http://1.bp.blogspot.com/_hkkBsHR0r5s/S2bvw5rZzgI/AAAAAAAAAL4/kNvsBcVm0co/s200/LocalServer7.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Test file permissions.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Now, set up your database connection:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;First, click on "SQL Server 2005/2008 Database" and wait for the screen to refresh&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Enter "(local)" for the Server&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Enter the database name, eg "dnn522pen"&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Uncheck "Integrated Security" and wait for the screen to refresh&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Enter the User ID and password&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Keep "Run as db Owner" checked&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If desired, enter a database table name qualifier, eg "DNN_"&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click on "Test Database Connection"&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If OK, click on Next&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_hkkBsHR0r5s/S2bvxHwvLmI/AAAAAAAAAMA/bge8bEmLOUk/s1600-h/LocalServer8.png"&gt;&lt;img style="WIDTH: 200px; HEIGHT: 168px; CURSOR: hand" id="BLOGGER_PHOTO_ID_5433293627645177442" border="0" alt="" src="http://4.bp.blogspot.com/_hkkBsHR0r5s/S2bvxHwvLmI/AAAAAAAAAMA/bge8bEmLOUk/s200/LocalServer8.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Proceed with the rest of the DNN installation as normal.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-3346076260305952308?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/3346076260305952308/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=3346076260305952308' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/3346076260305952308'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/3346076260305952308'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/02/dnn-local-install-with-sql-server.html' title='DNN local install with SQL Server'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_hkkBsHR0r5s/S2bviYI8U5I/AAAAAAAAALI/Sv40At0fMOw/s72-c/LocalServer1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-3091023214302048955</id><published>2010-01-15T15:25:00.007Z</published><updated>2010-01-23T12:35:17.870Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL server'/><title type='text'>Importing Identity column data into SQL Server</title><content type='html'>Updated 23/1/10:&lt;br /&gt;I am in the process of porting some (DNN module) tables from one Microsoft SQL Server database to another. For various reasons, I want the primary key identity column values to remain the same. This is on a shared SQL server, so we don't have admin access, eg to the command line and file system.&lt;br /&gt;&lt;br /&gt;The SQL Server Import and Export wizard transfers the data nicely and will preserve the identity values if you do it correctly.&lt;br /&gt;&lt;br /&gt;There are two crucial tricks:&lt;br /&gt;- set up the destination table(s) with the correct primary keys etc before the copy&lt;br /&gt;- in the wizard, click Edit Mapping and tick Enable Identity Insert.&lt;br /&gt;&lt;br /&gt;----------------------&lt;br /&gt;This is alternative technique which should no longer be needed:&lt;br /&gt;&lt;br /&gt;I first export the original data to a temporary database. This creates the tables but but does not create primary keys, set default values etc. However, the copy process does preserve the original primary key identity column values.&lt;br /&gt;&lt;br /&gt;By installing the DNN module on the destination system, the tables are created correctly there. However, a wizard import into these tables (even with "Enable identity insert" set) does not preserve the key values.&lt;br /&gt;&lt;br /&gt;The trick I found is to upload the data into a new table eg "Form2", and then use the following code to copy the data into the correct "Form" table. The trick is to use the "set identity_insert" statement.&lt;br /&gt;&lt;pre&gt;set identity_insert DNN_Form on;&lt;br /&gt;&lt;br /&gt;insert into DNN_Form ([FormID],[FormName])&lt;br /&gt;select * from DNN_Form2;&lt;br /&gt;&lt;br /&gt;set identity_insert DNN_Form off;&lt;/pre&gt;&lt;br /&gt;You must list all the required columns in the insert statement.&lt;br /&gt;Only one table can have identity_insert on at a time.&lt;br /&gt;&lt;br /&gt;Finally, delete the "Form2" table.&lt;br /&gt;&lt;br /&gt;You can check the current insert identity value before and after as follows:&lt;br /&gt;&lt;pre&gt;DBCC CHECKIDENT(DNN_Form);&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-3091023214302048955?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/3091023214302048955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=3091023214302048955' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/3091023214302048955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/3091023214302048955'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/01/importing-identity-column-data-into-sql.html' title='Importing Identity column data into SQL Server'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-9151985717387452405</id><published>2010-01-14T20:37:00.003Z</published><updated>2010-01-14T20:46:33.852Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='multi-boot'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows'/><title type='text'>Wandering drive letters in Windows</title><content type='html'>My main computer's motherboard died - long live the new computer!  Thankfully, all the data on the two existing drives were OK.  A hastily bought 3.5 inch USB drive caddy worked a treat, keeping me going on the laptop.&lt;br /&gt;&lt;br /&gt;The new computer was pre-installed with Windows 7 Home Premium on drive C with a separate partition on drive D for data.  I have an MSDN subscription and I wanted to set up various versions of Windows multi-booting on the same computer.  I have two different systems that I want to run normally, one with Visual Studio 2008 and the other with various older bits of software.  I also want to have some different versions of Windows for software testing, ideally Windows 7, Vista and XP, both in x86 and x64 versions.&lt;br /&gt;&lt;br /&gt;OK - I know I can run a virtual PC, but I want to use two of these systems regularly at top speed.  I haven't tried virtual PC yet, so I decided to stick with what I knew, which is the HyperOs multibooter, though I had to upgrade to the latest version.&lt;br /&gt;&lt;br /&gt;The first task was to repartition the disk, to give more partitions, each with a Windows installation, as well as big photo/video partition.  I found that there were already two extra partitions, a (Packard Bell?) Recovery Partition and a small System Reserved partition set up by Windows 7, ie 4 Primary partitions in total.  The W7 Disk Management tool wanted to convert my disk to Dynamic Disks to give me more partitions, but these cannot be used for booting, so a swift exit was called for.&lt;br /&gt;&lt;br /&gt;HyperOS mentioned the Acronis partitioner but this wasn't playing - not sure if it was Windows 7 or the size of the disk.  I did have a copy of gparted on CD, but this hadn't worked for me before.  Eventually I found Partition Wizard www.partitionwizard.com which has worked brilliantly.  I got a free commercial licence for this.  &lt;br /&gt;&lt;br /&gt;Using Partition Wizard, I deleted (empty) drive D and made quite a few other Logical partitions.  Partition Wizard is clever enough to know that it cannot do changes in some circumstances, and so does it on reboot.  Partition Wizard can also resize partitions, moving data if need be.  So far that's worked fine.  (Check your computer power settings don't shut your disk down at an awkward moment.)&lt;br /&gt;&lt;br /&gt;Having done that, I could now copy all my precious data onto the new hard disk from the drive caddy.&lt;br /&gt;&lt;br /&gt;Anyway, I've now done various Windows installations, some by installing from DVD from Windows, but mostly by installing from DVD at reboot, ie choosing DVD at boot up from the BIOS boot menu - and choosing Custom Setup to choose the install partition.  The problem that I have found is that the drive letters that Windows uses and sees seems to change a lot.  The original W7 system is on drive C on the "first" partition.  I have another W7 on the fifth partition which thinks of itself as being on drive M - fine.  However, most other W7 and Vista installations think of themselves as being drive C, even though they are on partition 4, 7 or 8.  When I have rebooted in one of these systems, the drive letters are assigned in a fairly random way.  The DVD drive is usually drive E but not always.&lt;br /&gt;&lt;br /&gt;In Windows Disk Management you change the drive letters - for some drives at least.  But there's limited scope for what you can change to.  Partition Wizard can do this, and is probably more successful.  However there appears to be no way of persuading a Windows on partition 4 (that thinks it is at drive C) to think of itself as being drive L for example.&lt;br /&gt;&lt;br /&gt;All these wandering drive letters might not be problem.  However my software development stuff and business data has always been carefully set up (for various reasons) to be stored on both drives C and D.  I don't tend to put data in "My Documents", "Documents", "Pictures", etc because these are (usually) stored in different locations for each version of Windows.  Anyway, I have persuaded partition 2 to be drive D in all the installations so far.  However it was a problem that partition 1 kept wandering all over the shop.  My solution was to move all my crucial data from drive C onto a bigger drive D.  Ok - fairly simple in itself, but I'm still having to work out what dependencies there are in all my scripts.&lt;br /&gt;&lt;br /&gt;Another complication in this process was that Windows XP was dying during installation (with a BSOD).  This turned out to be because the SATA drives were being accessed using AHCI.  Changing the BIOS to use the SATA setting "Native IDE" got the XP installation to work.  However I did not want to leave this setting as is, so I change it whenever I want to switch to XP.  XP also doesn't recognise many of the motherboard peripherals, eg Ethernet, so the installation is not very useful.  The option to press F6 during installation would let me install a suitable driver, but (a) I don't have the driver and (b) the system doesn't have a floppy; it does look as though new motherboard has a floppy interface, but there's no connector soldered in there!&lt;br /&gt;&lt;br /&gt;I was also able to add an IDE/PATA cable and drive to the system to connect my old drives, but the installation still did not work if I was in AHCI SATA mode.&lt;br /&gt;&lt;br /&gt;I've still many applications to configure and systems to set up, but I'm getting there.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-9151985717387452405?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/9151985717387452405/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=9151985717387452405' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/9151985717387452405'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/9151985717387452405'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2010/01/wandering-drive-letters-in-windows.html' title='Wandering drive letters in Windows'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-4111420789151486206</id><published>2009-09-25T11:49:00.017+01:00</published><updated>2011-09-17T09:05:02.131+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='open source'/><category scheme='http://www.blogger.com/atom/ns#' term='integration'/><category scheme='http://www.blogger.com/atom/ns#' term='vtiger'/><title type='text'>Starting to program vtiger CRM</title><content type='html'>&lt;p&gt;This article applies to vtiger 5.1.0 - some of the advice/code may not be accurate for later versions.&lt;/p&gt;&lt;br /&gt;&lt;p&gt;We've been investigating the vtiger CRM as a component in an open source set of tools that a business or organisation might need. A crucial idea is to link various tools together, so I have been looking at how to get information into and out of vtiger.&lt;/p&gt;&lt;p&gt;As a start, we're looking at an Outlook add-in to add an email address/name to the vtiger Contacts list, and how to export to an accounts package. There is already an Outlook vtiger add-in, but it seems pretty flaky and only does synchronisation; we want it to check a new email for existing matches and only add it if need be.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;About vtiger&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;vtiger is a Customer Relationship Manager (CRM). vtiger deals with Leads who might turn into Contacts, with a Contact associated with an Account. You can set up Products with stock levels (and Services as well) and then generate Quotes and Invoices which can be downloaded as PDFs or sent by email. All that sort of thing. It is important to know that vtiger is designed for use only by a business' staff - it is not intended to be used by customers, although vtiger can check incoming support emails.&lt;/p&gt;&lt;p&gt;vtiger is extensible and there is some basic documentation to get you started making your own modules. The instructions effectively describe how to create a new object type (eg a time sheet), represented as a table or two in the vtiger database. You can link your module and its new object type into vtiger, so that eg menu [Tools][Time sheet] shows a list of time sheets in the standard vtiger style, clicking on a record shows the detail, with an Edit option available.&lt;/p&gt;&lt;p&gt;For the rest of this piece, I'm looking at adding functionality to an existing module. This will hopefully help you even if you are starting a complete module. Be careful if you alter existing code - it may well be overwritten if you upgrade vtiger.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The vtiger database&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;You will almost certainly want to have access to the database that vtiger uses, partly so you can see what's in there, but also so you see any changes as they really are. For MySQL, using phpMyAdmin will probably be the available tool - make sure you can get in to your database.&lt;/p&gt;&lt;p&gt;If there aren't any already, make some test Contacts within vtiger. In phpMyAdmin have a look at these tables:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;vtiger_contactdetails&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;vtiger_contactscf&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;vtiger_contactaddress&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;vtiger_contactsubdetails&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Each of these tables has a column named 'contactid' or similar as a primary or foreign key. Note that the "contact_no" column is what is displayed is vtiger as the "Contact Id", eg 'CON1'.&lt;/p&gt;&lt;p&gt;The table &lt;span style="font-family:courier new;"&gt;vtiger_modentity_num&lt;/span&gt; is used when generating the next "contact_no" value, for the appropriate module, so 2 might be next free Contact number to make 'CON2'. Later, we'll use vtiger &lt;span style="color:#33cc00;"&gt;CRMEntity&lt;/span&gt; class &lt;span style="color:#33cc00;"&gt;setModuleSeqNumber()&lt;/span&gt; function to get this value.&lt;/p&gt;&lt;p&gt;The 'contactid' numbers identifying the contact don't start at 0 or 1. In fact, vtiger assigns a unique id number across all objects, so object 792 might be a Contact and 793 might be an Invoice. These details numbers are stored in the &lt;span style="font-family:courier new;"&gt;vtiger_crmentity&lt;/span&gt; table - a deleted flag in there is set when items are first deleted - so they can recovered from the recycle bin. Later, we'll use the &lt;span style="color:#33cc00;"&gt;insertIntoCrmEntity()&lt;/span&gt; function to get a new object id.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;vtiger command structure&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;All vtiger accesses come through one root file &lt;span style="font-family:arial;"&gt;index.php&lt;/span&gt;. The parameters determine which module operation is carried out. For example, consider this URL&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;/index.php?action=DetailView&amp;amp;module=Invoice&amp;amp;record=800&amp;amp;parenttab=Inventory&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It runs the code &lt;span style="font-family:arial;"&gt;DetailView.php&lt;/span&gt; in the directory &lt;span style="font-family:arial;"&gt;/modules/Invoice/&lt;/span&gt;. The code that runs in there, finds the "record" value and shows the relevant invoice. So, you can add to the "Detail View" functionality by adding code to this file.&lt;/p&gt;&lt;p&gt;You can create your own actions by making a new file in the relevant directory. You then need to provide a means of invoking that action - more later.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Smarty templates&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;vtiger uses the Smarty template system to generate its output. By and large, the vtiger code doesn't generate any HTML - instead, it loads up a template from TPL files in the &lt;span style="font-family:arial;"&gt;/Smarty/templates/&lt;/span&gt; directory. vtiger PHP code sets various values that are combined into the template to form the final output. Smarty has an extensive control language available for use in a template, so it can cope with complicated structures, eg it can iterate through arrays to generate multiple table rows. One Smarty template can invoke another, so finding where something is generated can be tricky.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Adding a tool option to an Invoice&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If you view an Invoice there are Tools listed on the right hand side: "Export to PDF" and "Send Email with PDF". To add another option here, you have to drill down through the tpl files: &lt;span style="font-family:arial;"&gt;DetailView.php&lt;/span&gt; invokes template &lt;span style="font-family:arial;"&gt;Inventory/InventoryDetailView.tpl&lt;/span&gt; which includes template &lt;span style="font-family:arial;"&gt;Inventory/InventoryActions.tpl&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;In &lt;span style="font-family:arial;"&gt;InventoryActions.tpl&lt;/span&gt;, after this line:&lt;/p&gt;&lt;pre&gt;&amp;lt;!-- To display the Export To PDF link for PO, SO, Quotes and Invoice - ends --&amp;gt;&lt;/pre&gt;&lt;p&gt;add in this code:&lt;/p&gt;&lt;pre&gt;{if $MODULE eq 'Invoice'}&lt;br /&gt;{assign var=send_accounts_action value="SendAccounts"}&lt;br /&gt;&amp;lt;tr&amp;gt;&amp;lt;td align="left" style="padding-left:10px;"&amp;gt;&lt;br /&gt;&amp;lt;a href="index.php?module={$MODULE}&amp;amp;action={$send_accounts_action}&amp;amp;return_module={$MODULE}&amp;amp;return_action=DetailView&amp;amp;record={$ID}&amp;amp;return_id={$ID}" class="webMnu"&amp;gt;&amp;lt;img src="{'actionGenerateInvoice.gif'@vtiger_imageurl:$THEME}" hspace="5" align="absmiddle" border="0"/&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;a href="index.php?module={$MODULE}&amp;amp;action={$send_accounts_action}&amp;amp;return_module={$MODULE}&amp;amp;return_action=DetailView2&amp;amp;record={$ID}&amp;amp;return_id={$ID}" class="webMnu"&amp;gt;Send to Accounts&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;{/if}&lt;/pre&gt;&lt;p&gt;Upload &lt;span style="font-family:arial;"&gt;InventoryActions.tpl&lt;/span&gt; and view an Invoice - check that "Send to Accounts" is visible on the right.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Adding to accounts&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Now, create a file &lt;span style="font-family:arial;"&gt;SendAccounts.php&lt;/span&gt; - probably copying an existing file such as &lt;span style="font-family:arial;"&gt;DetailView.php&lt;/span&gt; makes sense.&lt;/p&gt;&lt;p&gt;OK - this bit is skimpy for now, but it might get you started. Near the end, add in the following code to query the vtiger database and display a little info from each record found. To get the list of products ordered you will also have to query the &lt;span style="font-family:courier new;"&gt;vtiger_inventoryproductrel&lt;/span&gt; table.&lt;/p&gt;&lt;pre&gt;$query="select * from vtiger_invoice where invoiceid=?";&lt;br /&gt;$invoice_id = $focus-&gt;id;&lt;br /&gt;$result = $adb-&gt;pquery($query, array($invoice_id));&lt;br /&gt;$noofrows = $adb-&gt;num_rows($result);&lt;br /&gt;$final_output = "";&lt;br /&gt;while ($inv_info = $adb-&gt;fetch_array($result))&lt;br /&gt;{&lt;br /&gt;$invoice_no = $inv_info['invoice_no'];&lt;br /&gt;$subject = $inv_info['subject'];&lt;br /&gt;$invoicedate = $inv_info['invoicedate'];&lt;br /&gt;$accountid = $inv_info['accountid'];&lt;br /&gt;$total = $inv_info['total'];&lt;br /&gt;$final_output .= "$invoice_no: $subject $invoicedate $accountid $total&amp;lt;br/&amp;gt;";&lt;br /&gt;}&lt;br /&gt;$smarty-&gt;assign("SEND_DETAILS", $final_output);&lt;br /&gt;$smarty-&gt;display("Inventory/SendToAccounts.tpl");&lt;/pre&gt;&lt;p&gt;At the end of our new code, the Smarty variable "&lt;span style="color:#3366ff;"&gt;SEND_DETAILS&lt;/span&gt;" is set to the string we want to display. Then the Smarty template &lt;span style="font-family:arial;"&gt;SendToAccounts.tpl&lt;/span&gt; is run.&lt;/p&gt;&lt;p&gt;You had best create the template file &lt;span style="font-family:arial;"&gt;SendToAccounts.tpl&lt;/span&gt; initially as a copy of an existing template, eg &lt;span style="font-family:arial;"&gt;InventoryDetailView.tpl&lt;/span&gt;. You will need to pare it down - and eventually add in a line like the following to display your output:&lt;/p&gt;&lt;pre&gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;Storing data&amp;lt;/b&amp;gt;: {$SEND_DETAILS}&amp;lt;/p&amp;gt;&lt;/pre&gt;&lt;p&gt;OK - all we've done so far is display the invoice details. Actually talking to an accounts system is beyond the scope of this article.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Adding a new contact&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;As I said earlier, I want to have an Outlook add-in that can be used to add a contact to the vtiger database, after checking to see whether the contact already exists. As the first step to getting this done, I set up a small web form that has First name, Last name and Email fields. The Go button is set up to go to this vtiger URL eg:&lt;br /&gt;&lt;br /&gt;&lt;a&gt;&lt;span style="font-family:courier new;"&gt;/index.php?action=AddOutlookContact&amp;amp;module=Contacts&amp;amp;FirstName=Chris&amp;amp;LastName=Cant&amp;amp;Email=sales@phdcc.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;As per usual, vtiger uses its standard rules so that this script is called:&lt;br /&gt;&lt;span style="font-family:arial;"&gt;/modules/Contacts/AddOutlookContact.php&lt;/span&gt;&lt;/p&gt;&lt;p&gt;I made &lt;span style="font-family:arial;"&gt;AddOutlookContact.php&lt;/span&gt; in a similar way to that described above, using the title "Import from Outlook" for the main content tab. The code generates the output HTML in the PHP script - a neater final approach would put the raw HTML within the template. Anyway, the PHP code generates its HTML in &lt;span style="color:#33cc00;"&gt;$Output&lt;/span&gt; and then invokes my new template &lt;span style="font-family:arial;"&gt;AddOutlookContact.tpl&lt;/span&gt;:&lt;/p&gt;&lt;pre&gt;$smarty-&gt;assign("IMPORT_DETAILS", $Output);&lt;br /&gt;$smarty-&gt;display("AddOutlookContact.tpl");&lt;/pre&gt;&lt;p&gt;In the guts of &lt;span style="font-family:arial;"&gt;AddOutlookContact.tpl&lt;/span&gt;, the output is reproduced:&lt;/p&gt;&lt;pre&gt;&amp;lt;div&amp;gt;{$IMPORT_DETAILS}&amp;lt;/div&amp;gt;&lt;/pre&gt;&lt;p&gt;The tasks within &lt;span style="font-family:arial;"&gt;AddOutlookContact.php&lt;/span&gt; are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Get passed parameters&lt;/li&gt;&lt;li&gt;Check parameters&lt;/li&gt;&lt;li&gt;Find any matching names or emails&lt;/li&gt;&lt;li&gt;Show matches to user&lt;/li&gt;&lt;li&gt;Show Add Contact button&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So: the user can see any existing matches, and can decide whether or not to press the Add Contact button, which goes to another script &lt;span style="font-family:arial;"&gt;AddOutlookContact2.php.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;The SQL script to query the existing contacts is as follows:&lt;/p&gt;&lt;pre&gt;SELECT c.*, a.accountname, crm.deleted FROM vtiger_contactdetails AS c&lt;br /&gt;LEFT JOIN vtiger_account AS a USING (accountid)&lt;br /&gt;INNER JOIN vtiger_crmentity AS crm ON crm.crmid=c.contactid&lt;br /&gt;WHERE lastname LIKE '%$QuotedLastName%' OR email LIKE '%$QuotedEmail%'";&lt;/pre&gt;&lt;p&gt;Note that I also use the function &lt;span style="color:#33cc00;"&gt;mysql_real_escape_string()&lt;/span&gt; to ensure that the passed strings cope with single and double quotes etc.&lt;/p&gt;&lt;p&gt;The SQL links &lt;span style="font-family:courier new;"&gt;vtiger_contactdetails&lt;/span&gt; with the &lt;span style="font-family:courier new;"&gt;vtiger_crmentity&lt;/span&gt; table so the Deleted column can be picked up. The &lt;span style="font-family:courier new;"&gt;vtiger_account&lt;/span&gt; table is also included so any associated account name can be found.&lt;/p&gt;&lt;p&gt;The code goes through all the found matches and displays the results to the user. I provided a link to each contact - this is the code for the link:&lt;/p&gt;&lt;pre&gt;$MatchLink = "index.php?action=DetailView&amp;amp;module=Contacts&amp;amp;record=$Match_ContactId&amp;amp;parenttab=Support";&lt;/pre&gt;&lt;p&gt;The following form shows a suitable "Add new contact" button:&lt;/p&gt;&lt;pre&gt;&amp;lt;form action='index.php' method='post' onsubmit='VtigerJS_DialogBox.block();'&amp;gt;&lt;br /&gt;&amp;lt;input type='hidden' value='AddOutlookContact2' name='action' /&amp;gt;&lt;br /&gt;&amp;lt;input type='hidden' value='Contacts' name='module' /&amp;gt;&lt;br /&gt;&amp;lt;input type='hidden' value='$htmlLastName' name='LastName' /&amp;gt;&lt;br /&gt;&amp;lt;input type='hidden' value='$htmlFirstName' name='FirstName' /&amp;gt;&lt;br /&gt;&amp;lt;input type='hidden' value='$htmlEmail' name='Email' /&amp;gt;&lt;br /&gt;&amp;lt;input type='submit' value='Add new contact' /&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;/pre&gt;&lt;p&gt;&lt;span style="font-family:arial;"&gt;AddOutlookContact2.php&lt;/span&gt; has a similar core as before, except it generates HTML that shows what it has done. Here's what I did to add a new contact. This is probably not the recommended vtiger method, but it seems to get the job done.&lt;/p&gt;&lt;pre&gt;$adb-&gt;startTransaction();&lt;br /&gt;$NewContactNo = $focus-&gt;setModuleSeqNumber("increment",'Contacts'); // updates vtiger_modentity_num&lt;br /&gt;$focus-&gt;insertIntoCrmEntity('Contacts'); // updates vtiger_crmentity and sets $focus-&gt;id&lt;br /&gt;&lt;br /&gt;$query = "insert into vtiger_contactdetails (contactid,contact_no,firstname,lastname,email) values(?,?,?,?,?)";&lt;br /&gt;$qparams = array($focus-&gt;id, $NewContactNo, $FirstName, $LastName, $Email);&lt;br /&gt;$adb-&gt;pquery($query, $qparams);&lt;br /&gt;&lt;br /&gt;$query = "insert into vtiger_contactscf (contactid) values(?)";&lt;br /&gt;$qparams = array($focus-&gt;id);&lt;br /&gt;$adb-&gt;pquery($query, $qparams);&lt;br /&gt;&lt;br /&gt;$query = "insert into vtiger_contactaddress (contactaddressid) values(?)";&lt;br /&gt;$qparams = array($focus-&gt;id);&lt;br /&gt;$adb-&gt;pquery($query, $qparams);&lt;br /&gt;&lt;br /&gt;$query = "insert into vtiger_contactsubdetails (contactsubscriptionid) values(?)";&lt;br /&gt;$qparams = array($focus-&gt;id);&lt;br /&gt;$adb-&gt;pquery($query, $qparams);&lt;br /&gt;&lt;br /&gt;$adb-&gt;completeTransaction();&lt;/pre&gt;&lt;p&gt;Here are the steps:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Call &lt;span style="color:#33cc00;"&gt;setModuleSeqNumber()&lt;/span&gt; to get the next contact no eg CON2&lt;/li&gt;&lt;li&gt;Call &lt;span style="color:#33cc00;"&gt;insertIntoCrmEntity()&lt;/span&gt; to get the next contact id, eg 794&lt;/li&gt;&lt;li&gt;Insert the basic details into &lt;span style="font-family:arial;"&gt;vtiger_contactdetails&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Insert the contact id into &lt;span style="font-family:arial;"&gt;vtiger_contactscf&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Insert basic rows into &lt;span style="font-family:arial;"&gt;vtiger_contactaddress&lt;/span&gt; and &lt;span style="font-family:arial;"&gt;vtiger_contactsubdetails&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The output provides a suitable link to the new Contact.&lt;/p&gt;&lt;p&gt;Later, I made a real Outlook 2007 addin to call this vtiger code.  An extra button is shown on the ribbon when you read an email.  When you click the button, it picks up the sender's name and email address and shows the correct URL with parameters in the user's default browser.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-4111420789151486206?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/4111420789151486206/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=4111420789151486206' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/4111420789151486206'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/4111420789151486206'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/09/starting-to-program-vtiger-crm.html' title='Starting to program vtiger CRM'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-6066066824192111927</id><published>2009-08-26T15:28:00.010+01:00</published><updated>2010-03-17T20:48:48.457Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>DNN no container span causes validation error</title><content type='html'>If you turn off the DNN container for a module, then DNN by default inserts an extra SPAN that can cause a W3C validation error. One solution is to have a minimal container.&lt;br /&gt;&lt;br /&gt;I had turned off the "Display Container" option for some modules so that no title is displayed. (In Admin Edit mode, the default container is used so that you can get at settings etc.) The HTML produced by DNN looked like this, within the skin pane:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;a name="402"&amp;gt;&amp;lt;/a&amp;gt;/&lt;br /&gt;&amp;lt;span id="dnn_ctr402_ContentPane" class="DNNAlignleft"&amp;gt;&lt;br /&gt;&amp;lt;!-- Start_Module_402 --&amp;gt;&lt;br /&gt;&amp;lt;div id="dnn_ctr402_ModuleContent"&amp;gt;&lt;/span&gt;&lt;br /&gt;and then the module content.&lt;br /&gt;&lt;br /&gt;This causes a validation error because you cannot nest a DIV within a SPAN.&lt;br /&gt;&lt;br /&gt;My solution is not to use the "Display Container" option - instead, I created a container with no title and used that instead. In the replacement container, I moved the [ACTIONS] and [ICON] into the footer.  This NoTitleContainer container files are available here: &lt;a href="http://www.phdcc.com/download/DNN/NoTitleContainer.zip"&gt;http://www.phdcc.com/download/DNN/NoTitleContainer.zip&lt;/a&gt;.  Unzip in /Portals/_default/Containers/MinimalExtropy.  I'm not sure if you need to go to [Admin][Skins] to make the container visible.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-6066066824192111927?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/6066066824192111927/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=6066066824192111927' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/6066066824192111927'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/6066066824192111927'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/08/dnn-no-container-span-causes-validation.html' title='DNN no container span causes validation error'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-1231242258420840318</id><published>2009-06-25T22:26:00.005+01:00</published><updated>2009-06-26T09:36:56.589+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>DNN Event Viewer spider errors</title><content type='html'>If you are getting "Page Load Exception" errors in the DotNetNuke (DNN) [Admin][Event Viewer] with the UserAgent indicating a web crawling spider, then you need this fix:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;The fix: &lt;/strong&gt;Get the &lt;a href="http://www.oliverhine.com/DotNetNuke/Downloads.aspx"&gt;Browser Caps - v2&lt;/a&gt; by Oliver Hine. Unzip the file &lt;code&gt;App_Browsers.zip&lt;/code&gt; to get the file &lt;code&gt;OtherSpiders.browser&lt;/code&gt; - put this in the &lt;code&gt;App_Browsers&lt;/code&gt; directory of your DNN site. Note that your DNN site will automatically restart, so perhaps do this at a quiet time.&lt;br /&gt;&lt;br /&gt;Alternatively (if you want), you should be able to stop these spiders accessing your site using a suitable &lt;code&gt;robots.txt&lt;/code&gt; file.&lt;br /&gt;&lt;br /&gt;This patch is needed for DNN 4.9.4 and possibly earlier. I think that it may be fixed in DNN 5.1+.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Spiders fixed: &lt;/strong&gt;This file has fixes for these spiders: Baiduspider, Yandex, ia_archiver, Sphere, Feedfetcher-Google, Yanga, worio, zibber, &lt;strike&gt;twiceler, voliabot&lt;/strike&gt;, aisearchbot, robotgenius and R6_FeedFetcher.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Spiders not fixed:&lt;/strong&gt; However it doesn't fix TweetmemeBot - I failed to add support for this. Read this &lt;a href="http://mark.kolich.com/2009/04/tweetmemebots-invalid-user-agent-string.html"&gt;Tweetmeme&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Update: doesn't seem to work for Twiceler or voilabot.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;What's happening: &lt;/strong&gt;DNN code is asking ASP.NET for details of the browser capabilities. ASP.NET gets this from the UserAgent string. DNN asks for the major and minor version numbers of the browser and causes an exception if these cannot be determined from the UserAgent. ASP.NET uses various &lt;code&gt;.browser&lt;/code&gt; files (in the framework directories and in the web app's &lt;code&gt;App_Browsers&lt;/code&gt; directory) to work out what browsers can do. As the UserAgent provided by these spiders doesn't follow a standard regex pattern, ASP.NET cannot get the major and minor versions.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Thanks to: &lt;/strong&gt;&lt;a href="http://twitter.com/BarrySweeney"&gt;Barry Sweeney&lt;/a&gt; for his prompt help on Twitter - and Oliver Hine of course.&lt;br /&gt;&lt;br /&gt;Below is the DNN exception that I get before applying this fix:&lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Message: DotNetNuke.Services.Exceptions.PageLoadException: Value cannot be null. Parameter name: String ---&gt; System.ArgumentNullException: Value cannot be null. Parameter name: String at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer&amp;amp; number, NumberFormatInfo info, Boolean parseDecimal) at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info) at System.Web.Configuration.HttpCapabilitiesBase.get_MajorVersion() at DotNetNuke.UI.Utilities.ClientAPI.BrowserSupportsFunctionality(ClientFunctionality eFunctionality) at DotNetNuke.UI.Utilities.ClientAPI.get_ROW_DELIMITER() at DotNetNuke.UI.Utilities.ClientAPI.RegisterClientVariable(Page objPage, String strVar, String strValue, Boolean blnOverwrite) at DotNetNuke.UI.Skins.Controls.Search.Page_PreRender(Object sender, EventArgs e) at System.Web.UI.Control.OnPreRender(EventArgs e) at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) --- End of inner exception stack trace ---&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-1231242258420840318?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/1231242258420840318/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=1231242258420840318' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1231242258420840318'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1231242258420840318'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/06/dnn-event-viewer-spider-errors.html' title='DNN Event Viewer spider errors'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-9106045606662390258</id><published>2009-05-27T16:41:00.007+01:00</published><updated>2009-05-27T18:11:33.974+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><category scheme='http://www.blogger.com/atom/ns#' term='skin'/><title type='text'>DNN Skin token support in a module</title><content type='html'>This article shows how to get a DotNetNuke (DNN) module to support tokens in skins. It will use, as an example, how I added support for the [CODEMODULE] skin token in our &lt;a href="http://www.phdcc.com/phdcc.CodeModule/"&gt;phdcc.CodeModule&lt;/a&gt; retail module.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Introduction&lt;/strong&gt;: skins are used by DNN as a template for the whole site output, and a container template is used to wrap an individual module. Skins and containers can contain standard tokens such as [LOGO] - this is replaced at runtime by the logo image chosen by the site administrator.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Doing it manually&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;The crucial trick is to add a suitable entry to the &lt;span style="font-family:courier new;"&gt;ModuleControls&lt;/span&gt; table.&lt;br /&gt;&lt;br /&gt;This table primarily contains a mapping between the blank (View), Edit and Settings control keys for a module and the actual controls that implement them, eg the "Settings" ControlKey is set to "DesktopModules/phdcc.CodeModule/Settings.ascx" for my module.&lt;br /&gt;&lt;br /&gt;To support your new skin token, add a new row with your skin token name in the &lt;em&gt;ControlKey&lt;/em&gt; column (eg "CodeModule" with no quotes or brackets), a NULL for the &lt;em&gt;ControlTitle&lt;/em&gt;, your view control in &lt;em&gt;ControlSrc&lt;/em&gt; and -2 in &lt;em&gt;ControlType&lt;/em&gt;. For my module, the &lt;em&gt;ControlSrc&lt;/em&gt; is "DesktopModules/phdcc.CodeModule/View.ascx"&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Adding a token to a skin&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;People work with skins in various ways. The standard method is to have an HTML template file that is parsed into an ASCX when the skin is uploaded. If you edit the HTML file on a live site then you can click on [Parse Skin Package] to re-make the ASCX.&lt;br /&gt;&lt;br /&gt;Anyway, add your skin token to your HTML skin somewhere, eg add "[CODEMODULE]", without quotes but with brackets - and in upper case. Either package it up and upload, or edit the live version and click on [Parse Skin Package]. The parse log should show your token being replaced. The ASCX should have your token replaced, as per the example below.&lt;br /&gt;&lt;br /&gt;If you work with an ASCX template file, then you need to add a suitable Register directive at the top of the ASCX, eg:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;%@ Register TagPrefix="dnn" TagName="CODEMODULE" Src="~/DesktopModules/phdcc.CodeModule/View.ascx" %&amp;gt;&lt;/span&gt;&lt;br /&gt;then add instances of this control later on, eg:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;dnn:codemodule runat="server" id="dnnCODEMODULE" /&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Hopefully your view control should now be called whenever this skin is used. If so, hurrah! Note that the PortalModuleBase TabModuleId is set to -1 so you could use this to tell when you are being called from a skin.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Token parameters&lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;It is useful to be able to pass parameters to your module, eg so it knows what to display. The parameters are set in the skin.xml file alongside your HTML template. For my module, I wanted to add a ControlFile parameter to tell my module what to do. I added this to skin.xml, after &amp;lt;Objects&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;object&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;token&amp;gt;[CODEMODULE]&amp;lt;/token&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;settings&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;setting&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;name&amp;gt;ControlFile&amp;lt;/name&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;value&amp;gt;viewSkin.ascx&amp;lt;/value&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/setting&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;/settings&amp;gt;&lt;br /&gt;&amp;lt;/object&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;You can specify several settings for your token.  In this case, I set the "ControlFile" setting to value "viewSkin.ascx". Make sure [CODEMODULE] is in upper case.&lt;br /&gt;&lt;br /&gt;To support each parameter, you must add a corresponding property to your control.  So, in this case, I must add a property called "ControlFile".  In VB, this could be:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Private _ControlFile As String&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Public Property ControlFile() As String&lt;br /&gt;&amp;nbsp;&amp;nbsp;Get&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Return _ControlFile&lt;br /&gt;&amp;nbsp;&amp;nbsp;End Get&lt;br /&gt;&amp;nbsp;&amp;nbsp;Set(ByVal value As String)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_ControlFile = value&lt;br /&gt;&amp;nbsp;&amp;nbsp;End Set&lt;br /&gt;End Property&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;If you upload or re-parse your skin, you should find that the parameter has now been set in the ASCX, eg:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;dnn:codemodule runat="server" id="dnnCODEMODULE" ControlFile="viewSkin.ascx" /&amp;gt; &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;When your view control is now run, the ControlFile property should have been set before your Page_Load is called.  You can use this to tell when you have been called from a skin - and act accordingly.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Automatic installation&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Earlier on, we added the crucial row to the &lt;span style="font-family:courier new;"&gt;ModuleControls&lt;/span&gt; table by hand.  To set this automatically during installation, you need to add an appropriate data provider.  For example, this might be called 01.00.00.SqlDataProvider.  Add in SQL code to delete any existing ControlKey and add the correct one, eg:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;DELETE FROM {databaseOwner}{objectQualifier}ModuleControls&lt;br /&gt;&amp;nbsp;&amp;nbsp;WHERE ControlKey='CodeModule'&lt;br /&gt;INSERT INTO {databaseOwner}{objectQualifier}ModuleControls&lt;br /&gt;&amp;nbsp;&amp;nbsp;(ControlKey,ControlTitle,ControlSrc,ControlType)&lt;br /&gt;&amp;nbsp;&amp;nbsp;VALUES ('CodeModule',NULL,'DesktopModules/phdcc.CodeModule/View.ascx',-2) &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Finally, add in an entry to your Uninstall.SqlDataProvider file to remove this entry if your module is removed:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;DELETE FROM {databaseOwner}{objectQualifier}ModuleControls&lt;br /&gt;&amp;nbsp;&amp;nbsp;WHERE ControlKey='CodeModule'&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-9106045606662390258?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/9106045606662390258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=9106045606662390258' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/9106045606662390258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/9106045606662390258'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/05/dnn-skin-token-support-in-module.html' title='DNN Skin token support in a module'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-8375722092908504633</id><published>2009-04-14T13:49:00.005+01:00</published><updated>2009-04-14T14:20:27.354+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>DNN development and production sites</title><content type='html'>Bro John wants me to write up how he does DotNetNuke (DNN) upgrades and site copies.  As a preamble, he wrote this:&lt;br /&gt;=============&lt;br /&gt;I would think anyone with a production site would want three copies&lt;br /&gt;A - a live site&lt;br /&gt;B - a transition site&lt;br /&gt;C - a development site&lt;br /&gt;&lt;br /&gt;also&lt;br /&gt;D - a trash site&lt;br /&gt;&lt;br /&gt;As most of my stuff is now in &lt;a href="http://www.phdcc.com/phdcc.CodeModule/"&gt;phdcc.CodeModule&lt;/a&gt;s, I develop these until they work on C, I copy these to D to see if they still work in a different environ. I then copy them to A and test.&lt;br /&gt;&lt;br /&gt;I would prefer to have a B which means I can shut down A and have users running on B and then swap back to A later if for example I do a DNN version upgrade.&lt;br /&gt;&lt;br /&gt;Doing a DNN version upgrade on a live site is just asking for trouble.&lt;br /&gt;&lt;br /&gt;If this sort of stuff is not sorted in DNN properly then I wouldn't consider it a solid environment to do anything serious in.&lt;br /&gt;&lt;br /&gt;I am trying to be careful about keeping all database/email server specific stuff in a few files.  Hard links to other pages on the site are OK if you copy the entire site correctly.&lt;br /&gt;&lt;br /&gt;Maybe bigger players have other tricks they pull. Maybe they swap DNS pointers. However that takes time to permeate and produces horrid cahcing problems.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-8375722092908504633?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/8375722092908504633/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=8375722092908504633' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8375722092908504633'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/8375722092908504633'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/04/dnn-development-and-production-sites.html' title='DNN development and production sites'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-1729941238407584543</id><published>2009-04-03T12:30:00.002+01:00</published><updated>2009-04-03T12:42:37.878+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IIS'/><title type='text'>Content-Location HTTP header for current URL</title><content type='html'>All our web sites on shared host provider Crystaltech/Newtek have just started serving an extra HTTP header &lt;code&gt;Content-Location&lt;/code&gt; for all static web page requests - the header contains the current URL.&lt;br /&gt;&lt;br /&gt;To see this in action, use &lt;a href="http://www.rexswain.com/httpview.html"&gt;Rex Swain's HTTP Viewer&lt;/a&gt; to view this page at our web site: &lt;a href="http://www.phdcc.com/phd.html"&gt;http://www.phdcc.com/phd.html&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you look in the received output, you will see that the &lt;code&gt;Content-Location&lt;/code&gt; header is set to the requested URL:&lt;br /&gt;&lt;code&gt;Content-Location:·http://www.phdcc.com/phd.html(CR)(LF)&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Normally, the &lt;code&gt;Content-Location&lt;/code&gt; header is used to indicate when the content actually corresponds to another URL. So if you look at http://www.phdcc.com/ you will see this output:&lt;br /&gt;&lt;code&gt;Content-Location:·http://www.phdcc.com/default.htm(CR)(LF)&lt;/code&gt;&lt;br /&gt;ie the web site home page is actually called &lt;code&gt;default.htm&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;While this is a harmless change for most users, it did in fact fool our FindinSite-MS search engine - I am in the process of releasing a new version to cope with this. Our ISP Crystaltech claims that nothing has changed. Has anyone any further information?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-1729941238407584543?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/1729941238407584543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=1729941238407584543' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1729941238407584543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/1729941238407584543'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/04/content-location-http-header-for.html' title='Content-Location HTTP header for current URL'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-5446898403530858851</id><published>2009-04-02T15:36:00.013+01:00</published><updated>2009-04-02T21:33:28.706+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ASP.NET'/><title type='text'>Lowering ASP.NET memory usage</title><content type='html'>This blog post is a work in progress on how to keep ASP.NET web application memory usage low.  The motivation for this is to avoid the web app being stopped for using too much memory.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Background&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;The Microsoft Internet Information Services (IIS) web server has administrator options to &lt;a href="http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/1652e79e-21f9-4e89-bc4b-c13f894a0cfe.mspx"&gt;Recycle Worker Threads&lt;/a&gt;, ie to automatically stop a thread that runs an ASP.NET web application if a threshold is passed.  Some of these thresholds are 'arbitrary', eg the default Elapsed Time of 29 hours.  However the Virtual Memory and Used Memory thresholds tend to kick in as you use more memory.  If a web app is 'recycled' then you get no warning - the thread is simply terminated; the web app is only restarted in response to another web request.&lt;br /&gt;&lt;br /&gt;In addition, a web app will tend to slow down as its memory use increases.&lt;br /&gt;&lt;br /&gt;I am working on this topic for my &lt;a href="http://www.phdcc.com/findinsite/"&gt;FindinSite-MS site search engine&lt;/a&gt;.  This one web app both (a) does searches and (b) crawls a web site to build a 'search database' that is used by the search.  The crawl/index task is done in a separate background thread - an indexing task is either started from the user interface, or run on a predefined schedule.  (The app has to be alive for a scheduled index to run - so an outside process can be set up to wake up a web app if need be.)&lt;br /&gt;&lt;br /&gt;This software currently suffers from two problems:&lt;br /&gt;- Too much of the database-being-searched is kept in memory&lt;br /&gt;- More significantly: The index process uses a large amount of memory.&lt;br /&gt;&lt;br /&gt;Note that my 'search database' db (as I refer to it) is just a set of files in memory - no real database is used.  This makes the search engine easier to deploy as a database is not required.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Memory heuristics&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;I use the System.GC.GetTotalMemory(false) call to get the current total memory usage, without forcing a garbage collection.&lt;br /&gt;&lt;br /&gt;I don't have a precise figure for what amount of memory is too big.  On our Crystaltech shared host, anything less than 10MB is good, while anything over 100MB is bad - though a recent indexing run worked with a maximum 400MB memory usage.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Redesign methodology&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;My initial focus was on designing a new 'search database'.  This db design needs simultaneously to be searchable and buildable, ie sufficently fast and low-memory while searching - and the same while building.&lt;br /&gt;&lt;br /&gt;The main obvious programming technique is &lt;strong&gt;not to keep anything in memory&lt;/strong&gt;.  This is actually quite a hard mindset to achieve.  For example, it would be quite nice to keep a bit of information in memory about every file indexed, eg URL, title, size, etc.  While this might work for 1,000 files, or even 25,000, it is not going to work for half a million.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Using disk instead of memory&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If I cannot store information in memory, then I'll have to save it to disk.  In fact, this may not be as bad an option as it sounds, as the operating system (and hardware etc) will cache disk data in memory, so performance time may not go down too significantly.  Storing data on disk should not impact on ASP.NET memory usage, so should help ensure that my app isn't killed.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;&lt;strong&gt;Example 1: word file list&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;I want to store a list of file numbers for each word found during the crawl.  My first re-design had 32 of these numbers in memory, along with 4 other integers - with the rest on disk.  A second redesign reduced this to 8+4.  My latest design simply has 2 integers per word in memory.  Everything else is on disk.&lt;br /&gt;&lt;br /&gt;The first integer is the block number in the temporary data file.   The second number is the last inserted file number - this makes sure that I don't update the block more than once per file for each word.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;&lt;strong&gt;Example 2: file list&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;I want to know whether I've indexed a file before.  I used to keep 2 (yes two) lists of files in memory.  Now this is all written out to disk.&lt;br /&gt;&lt;br /&gt;So how do I check quickly if I've already indexed a file?  OK, I do have a List&amp;lt;&amp;gt; of all files indexed.  Each List element is a structure that contains two integers, a FileHash and a FilePointer.  The FileHash is the HashCode of the file path, and the FilePointer is the location of the full information on disk.&lt;br /&gt;&lt;br /&gt;To check whether I've indexed a file, I find the HashCode of the file path.  I then iterate through the List&amp;lt;&amp;gt;.  If the hash matches then I use the FilePointer to retrieve the path from disk.  If this matches, then the file has been indexed before.  I keep looking if it doesn't match, in case two or more files have the same hash.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;&lt;strong&gt;Example 3: reversed word list&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;To support wild cards at the start of a search, eg a search for [*hris], I need to reverse each word, so that "chris" becomes "sirhc".  [I won't reveal my algorithm just now.]&lt;br /&gt;&lt;br /&gt;At one stage, I created a list of reversed words as I went through the normal word list.  However, I now write the reversed words out to disk first.  I then clear the word list (see below) to reduce my memory footprint.  Finally I read my reversed words in for processing.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;&lt;strong&gt;Example 4: SortedDictionary.Clear()&lt;/strong&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;I use a SortedDictionary during the indexing run.  If I set this to null and do a garbage collect, then no memory is freed.  If I call Clear() and then set this to null, the memory is cleared.  [And I'm pretty certain that there are no other references to items in the dictionary.]&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Should I use Cache?&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Is it safe to use the ASP.NET Page.Cache?  I presume that using this will still add to the memory used by the application, so the web app could still be shut down unilaterally, without trying to clear the Cache.&lt;br /&gt;&lt;br /&gt;I do use the Cache as a part of the search process.  However I set an expiry time of 5 minutes - this provides a useful cache while a user is searching, but clears the memory when they have probably gone anyway.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Storing data in the Session&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;I have just remembered that I store the search results for each user in a Session variable.  This is useful, eg when they ask for the second page of hits for a search.  This results data could be reasonably big, so I now think that this is not wise.  The Session variable will presumably be cleared after say 20 minutes, but this is too big a risk.  I'll have to store the results to disk, retrieve and clear as necessary.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Large Object Heap objects&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;This article on &lt;a href="http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/"&gt;The Dangers of the Large Object Heap&lt;/a&gt; says that any object larger than 85kB (or 8kB for doubles) might result in increased memory usage.  Try not to use large objects.&lt;br /&gt;&lt;em&gt;Does each collection or generic collection count as one object, or is each one a multitude of its individual components?&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Current progress&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;A crawl of a 100,000 simple HTML files now takes 11 minutes and uses a maximum of 23MB of memory.  When this db is loaded for searching, the rest state of the web app is 6MB.  After a couple of searches this went up to 22MB.&lt;br /&gt;&lt;br /&gt;The previous version took 83 minutes and used 126MB max memory.  When loaded for searching, 44MB of memory is used, going up to 50MB after a couple of searches.&lt;br /&gt;&lt;br /&gt;This is not exactly a completely fair test as the new code is not complete in several important ways.  However it does show dramatic memory usage reductions.  I am not exactly sure why there's such a dramatic speed improvement - it might be because of the reduced memory usage - or it could be the simpler not-really-complete algorithm.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-5446898403530858851?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/5446898403530858851/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=5446898403530858851' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/5446898403530858851'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/5446898403530858851'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/04/lowering-aspnet-memory-usage.html' title='Lowering ASP.NET memory usage'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-32442424.post-5858332846256294215</id><published>2009-03-29T13:40:00.007+01:00</published><updated>2009-03-31T09:25:57.173+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DotNetNuke'/><category scheme='http://www.blogger.com/atom/ns#' term='DNN'/><title type='text'>DNN portal Import/Export Link Url bug</title><content type='html'>In DNN DotNetNuke 4.92, you can use [Host][Portals] to export a complete Portal as a template to the Portals/_default/ directory.  You can then import the template elsewhere using [Admin][Site Wizard].&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Summary:&lt;/strong&gt; if you have pages that have a "Link Url" that refers to another page on the site, then these links do not survive the import/export process properly.  Currently, you will need to fix these up by hand after the import, going through [Admin][Pages] to edit the selected page's settings.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Preamble:&lt;/strong&gt; in DNN, each page is identified by an integer TabId.  This nomenclature stems from DNN's initial versions that refered to each page as a "tab".&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Details:&lt;/strong&gt; When a "Link Url" is set up to another page on the site, the page's TabId is stored as an integer in the Tabs database table Url column.  When the portal is exported, this Url TabId number is stored in the template (in a &amp;lt;url&amp;gt; XML element).  On import, the Url TabId is simply stored directly in the new page row Url column.  However, the TabId of the desired "Link Url" page will almost certainly have changed, so the page will redirect the user to the wrong page.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Background:&lt;/strong&gt; The Site Wizard does not really describe what it does very well.  An old book says that the import process is *additive*, ie the pages in the template are *added* to the existing pages on your site.  However this isn't entirely correct: if you select "Replace" in the wizard, then the existing pages are soft-deleted, ie their TabName has "_old" appended and the page is marked as Deleted.&lt;br /&gt;&lt;br /&gt;The import Site Wizard assigns a new TabId to each imported page (if a new page is created).  This means that it is virtually certain that the TabId-s have changed.  If your template etc has hard-coded links to particular TabId-s, then these are almost guaranteed to be wrong.&lt;br /&gt;&lt;br /&gt;It would be useful if it were possible to import a template and keep the original TabId-s.  Currently, the portal export does not contain the current TabId for each page.  Superficially it is also not possible to keep existing TabId-s as the Tab table TabID column is an identity.  However it is possible to use the &lt;a href="http://msdn.microsoft.com/en-us/library/aa259221(SQL.80).aspx"&gt;SET IDENTITY_INSERT&lt;/a&gt; command to insert a row with a particular TabId.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://support.dotnetnuke.com/issue/ViewIssue.aspx?id=9632&amp;PROJID=2"&gt;Reported on DNN Gemini bug tracker&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;To do:&lt;/strong&gt;&lt;br /&gt;- See if there is a better way of putting links (eg in Text/HTML and in code) so that they survive the import/export process.  &lt;em&gt;Me: in code you can do this using the TabController GetTabByName function.&lt;/em&gt;&lt;br /&gt;- Double-check what the Ignore and Merge site wizard options do&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Bro John adds:&lt;/strong&gt;&lt;br /&gt;- These problems typically cause an infinite redirect loop and that IE does not notice this but FF and Chrome do.  &lt;em&gt;Me: The problem in one case is that the Login page was not visible to anonymous users, so it redirects to the Login page ad mausoleum.  The import process does not reset the LoginTabId; it only sets it if a tab with tabtype logintab is found.  Unless the old LoginTabId is Replaced, this should be safe.&lt;/em&gt;&lt;br /&gt;- The logon menu item typically fails so you need to have added a hand made login page to be able to log in to the site once you have created it ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/32442424-5858332846256294215?l=chriscant.phdcc.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chriscant.phdcc.com/feeds/5858332846256294215/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=32442424&amp;postID=5858332846256294215' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/5858332846256294215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/32442424/posts/default/5858332846256294215'/><link rel='alternate' type='text/html' href='http://chriscant.phdcc.com/2009/03/dnn-portal-importexport-link-url-bug.html' title='DNN portal Import/Export Link Url bug'/><author><name>Chris Cant</name><uri>http://www.blogger.com/profile/11367082039820244178</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='29' height='32' src='http://bp0.blogger.com/_hkkBsHR0r5s/SIidQwvn23I/AAAAAAAAABA/1Xq-6vG4S7g/S220/chris19.jpg'/></author><thr:total>1</thr:total></entry></feed>
