Getting Started with DotNetNuke 6, Windows 7, and IIS7.5

April 27, 2012

I wrote about DotNetNuke as a web application earlier this year, but did not document how to get started developing DotNetNuke modules. Since a simple web search delivers unfiltered results and information on versions 3 thru 6, let’s catch up on the infrastructure needed for DotNetNuke 6::

  • Download the MSBuild Community Tasks, an extension to the VS build utility
  • Download DNN (New Install or VS Starter Kit) (I am working with 6.1.4, in the meantime 6.1.5 has been released)
  • Download the DNN Module Development Template and follow the installation instructions
  • Install the MSBuild tasks
  • Unzip DotNetNuke.zip into a new directory (under wwwroot or under your development directory)
  • Right click on the DNN directory and assign full control for the IIS_IUSRS user (this is the system user that IIS typically uses to access the file system)

In IIS Manager

  • create a web application pointing to the DNN directory
  • select the web application
  • open the .Net Compilation feature, and set Debug = true
  • in the Basic Settings dialog, make sure an Application Pool with .Net 2.0 Framework is assigned (Integrated Mode seems to work find)
  • select Deactivate Ping in Application Pool to avoid timeouts during debugging

The debugging-related settings are necessary to avoid the error message

The web server is not configured correctly. See help for common configuration errors. Running the web page outside of the debugger may provide further information.

when trying to debug from Visual Studio.

Use SQL Server or SQL Server Express as your database server and

  • make sure SQL Authentication is enabled
  • create a database for DNN
  • create a login for the DNN user
  • create a user in the DNN database with the newly created login
  • update the TWO connection strings in the DNN web.config file

Point your browser to the web application to make sure the settings are correct, and step thru the installation procedure of DNN.

Start Visual Studio (both 2008 and 2010 seem to work with the project template). To create a DNN module, execute:

  • Create project “DotNetNuke C# Compiled Module” inside DesktopModules directory of the DNN installation
  • Adjust project settings according to generated Documentation.html
  • See also the original blogs here and here describing the usage of the template.
  • Edit the project’s .dnn file (copyright, license, readme, etc.)
  • Implement the module’s functionality
  • Switch to Release mode for 1st deployment and Build
  • Build in release mode will generate two zip files in the packages directory, namely [modulename][version]_Install.zip and [modulename][version]_Setup.zip
  • In DNN, login as Host, select Host menu, Extensions, hover over the Manage button, start the Install Extension Wizard, and upload the Source.zip from the DesktopModules/MyModule/packages directory
  • Installation should not produce any error messages. Click Return.
  • Since the uploaded files overwrite the original source files, VS will alert you with a “project changed” message.

That’s it!

Modules can be assigned to categories, but this assignment is performed by the DNN administrator, not the developer.

  • In the Admin menu, select Taxonomy
  • Click the Edit icon on entry Module_Categories
  • Press ”Add Term” to add new module category, input the category name, and hit ”Create New Term”
  • Press “Update” to save
  • In Host/Extensions,
  • Click the Edit icon on module
  • Select the desired Module Category
  • Scroll down and press ”Update Extension”

Simplifying CRM Queries using Object Initializers

April 25, 2012

When I joined my first CRM project more than half a year ago, I found the way to create queries quite tedious:

  • create the condition expressions
  • create the filter expression
  • optionally create the link expressions
  • create the query expression

leading to endless pages of query construction code, as illustrated in this MSDN sample and elsewhere.

Fortunately I found that you can streamline CRM4 queries by using object initializers introduced in VS 2008. No more proliferation of temporary variables storing query parts

public contact GetContactUniqueByName(Guid organisationId, 
  string firstname, string lastname, string email)
{
  QueryExpression query = new QueryExpression {
    EntityName = EntityName.contact.ToString(),
    ColumnSet = new ColumnSet {
      Attributes = contactColumns
    },
    Criteria = new FilterExpression {
      FilterOperator = LogicalOperator.And,
      Conditions = new ConditionExpression[] { 
        new ConditionExpression {
          AttributeName = "parentcustomerid",
          Operator = ConditionOperator.Equal,
          Values = new object[] { organisationId }
        },
        new ConditionExpression {
          AttributeName = "firstname",
          Operator = ConditionOperator.Equal,
          Values = new string[] { firstname },
        },
        new ConditionExpression {
          AttributeName = "lastname",
          Operator = ConditionOperator.Equal,
          Values = new string[] { lastname }
        },
        new ConditionExpression {
          AttributeName = "emailaddress1",
          Operator = ConditionOperator.Equal,
          Values = new string[] { email }
        }
      }
    }
  };
  ...
}

Trying to apply the same strategy to CRM2011, I found that the Conditions property is no longer assignable, which means you cannot pass the ConditionExpression array as object initializer.

A little helper method works around this new restriction

public FilterExpression CreateFilter(LogicalOperator op, 
    params ConditionExpression[] conditions)
{
  var filter = new FilterExpression(op);
  filter.Conditions.AddRange(conditions);
  return filter;
}

allowing the above query to be re-written (using the shortened ConditionExpression constructor) for CRM2011 as:

public contact GetContactUniqueByName(Guid organisationId, 
    string firstname, string lastname, string email)
{
  QueryExpression query = new QueryExpression
  {
    EntityName = Contact.EntityLogicalName,
    ColumnSet = new ColumnSet(true),
    Criteria = CreateFilter(LogicalOperator.And,
        new ConditionExpression(
          "parentcustomerid", ConditionOperator.Equal, organisationId),
        new ConditionExpression(
          "firstname", ConditionOperator.Equal, firstname),
        new ConditionExpression(
          "lastname", ConditionOperator.Equal, lastname),
        new ConditionExpression(
          "emailaddress1", ConditionOperator.Equal, email)
	)
  };

  ...
}

Retrieving the Attribute Names of CRM 2011 Entities

April 25, 2012

In CRM 2011, the CrmSvcUtil tool generates C# proxy classes for your CRM 2011 entities.

These classes are derived from the Entity class of the CRM SDK, the entities’ attributes are marked up with an AttributeLogicalName attribute which stores the attribute’s name in the database.

For queries, we need to list these attribute names, and it would be nice if the compiler used the data in the attributes to implement a type-safe compile-safe retrieval of attribute names by providing the properties of the C# classes.

Based on my code to retrieve property names from LinQ expressions, I wrote an extension method to retrieve the attribute names using the AttributeLogicalName attribute:

public static class EntityExtensions
{
  public static string GetAttributeName<T, P>(this T record, 
                          Expression<Func<T, P>> attribute) 
    where T : Entity
  {
    MemberExpression memberExpression = 
      (MemberExpression)attribute.Body;
    var member = memberExpression.Member;

    var nameAttributes = member.GetCustomAttributes(
      typeof(AttributeLogicalNameAttribute), true);
    if (nameAttributes != null && nameAttributes.Length > 0)
    {
      var logicalName = 
        (nameAttributes[0] as AttributeLogicalNameAttribute)
        .LogicalName;
      return logicalName;
    }
    throw new ArgumentException(string.Format(
      "{0} is not a CRM property of entity {1}", 
      member.Name, typeof(T).Name));
  }
}

We can then write a sample program like this

static void Main(string[] args)
{
  var account = new Account();
  Console.WriteLine(account.GetPropertyName(a => a.AccountId));
  Console.WriteLine(account.GetAttributeName(a => a.AccountId));
  Console.WriteLine(account.GetPropertyName(a => a.AccountNumber));
  Console.WriteLine(account.GetAttributeName(a => a.AccountNumber));
}

resulting in this output

AccountId
accountid
AccountNumber
accountnumber

Using this extension method, we can write a type-safe ColumnSet helper to provide the parameters for the built-in ColumnSet constructor:

public class ColumnSet<T> where T: Entity
{
  List<string> columns = new List<string>();

  public ColumnSet<T> Add<P>(Expression<Func<T, P>> expression)
  {
    columns.Add(EntityExtensions.GetAttributeName(default(T), 
                                                  expression));
    return this;
  }

  public string[] Columns { get { return columns.ToArray(); } }
}

The .Add() method implements a Fluent interface as it returns the object, allowing for concatenation of .Add() calls:

var columns = new ColumnSet<Account>()
  .Add(a => a.AccountId)
  .Add(a => a.AccountNumber);

The result can be passed to the ColumnSet constructor:

var columnset = new ColumnSet(columns.Columns);

Vintage Computing Anniversaries

April 25, 2012

The PC turned 30 last summer, and this week, the Sinclair ZX Spectrum turned 30 as well.

My personal perspective is the other way ’round: ignoring the couple of weeks I “enjoyed” the ZX81 (until the keyboard became unusable), the ZX Spectrum was my first computer that I actively programmed on (read: BASIC and tape recorder). Years later I switched to the Sinclair QL, and finally to the PC world (good-bye SuperBASIC, hello Turbo Pascal). By the way, TP’s 30th birthday is just a few months ahead.

Since then, computers have become bigger and fast enough that you can run a ZX Spectrum emulation in JavaScript in a browser on an operation system that requires a million times more disk space than the original computers had memory available!

Despite all that progress, the past is still with us, and will be so for quite some time ;)

Update: TheRegister has a special on eight-bit classic games featuring mostly the Spectrum.


Using ABCpdf in a Windows Service

April 16, 2012

One of my long-term projects is a web application which can generate PDF documents on the fly, but bigger PDFs can also be generated offline using a Windows service. Both the web application and the service use the same code base to invoke the ABCpdf library.

Recently, an update of Server 2008 to IE9 forced me to switch to ABCpdf’s Gecko engine, and this switch caused the service, which runs under the Local System account, to fail unexpectedly.

The Event Log contained various error entries:

Source ABCpdf

The following information was included with the event:
Failed to add HTML: Page load timed out.

Source Application Error

Faulting application MyService.exe, version 1.0.0.0, time stamp 0x4f883a56,
faulting module KERNEL32.dll, version 6.0.6002.18005, time stamp 0x49e037dd,
exception code 0xe0434f4d, fault offset 0x0003fbae, process id 0x%9, application start time 0x%10.

Source ASP.Net 2.0.50727.0

An unhandled exception occurred and the process was terminated.

After adding some logging, the following exceptions were reported

Exception: System.Runtime.Serialization.SerializationException

Message: Type ‘WebSupergoo.ABCpdf8.Internal.Spawntaneous.SpawntaneousException’ in Assembly ‘ABCpdf, Version=8.1.0.1, Culture=neutral, PublicKeyToken=a7a0b3f5184f2169′ is not marked as serializable.

StackTrace:    at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter)
at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.SerializeObject(Object obj, MemoryStream stm)
at System.AppDomain.Serialize(Object o)
at System.AppDomain.MarshalObject(Object o)

WebSupergoo.ABCpdf8.Internal.PDFException: Failed to add HTML: Page load timed out.

   at WebSupergoo.ABCpdf8.Internal.Gecko.GeckoCallback.MyCallback(IntPtr closure, IntPtr serializedOpts, UInt32 serializedOptsLen, Double widthInMm, Double heightInMm, String url, UInt32& outNumCommands, IntPtr& outSerializedData, UInt32& outSerializedDataLen)
at WebSupergoo.ABCpdf8.Internal.NDoc.N.AddImageUrlGecko_32(IntPtr doc, String url, Int32 width, IntPtr callback, String& outErr)
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.AddUrl(Doc doc, String url, Boolean paged, Int32 width, String& err)
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.AddHtml(Doc doc, String html, Boolean paged, Int32 width, String& err)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html, Boolean paged, Int32 width, Boolean disableCache)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html)

System.TypeInitializationException: The type initializer for ‘WebSupergoo.ABCpdf8.Internal.Gecko.GeckoCallback’ threw an exception. —> WebSupergoo.ABCpdf8.Internal.Spawntaneous.SpawntaneousException: Failed to generate worker process.

   at WebSupergoo.ABCpdf8.Internal.Spawntaneous.ProcessGenerator.GenerateLocalInvoker(MethodInfo method, String filename, Platform platform, TargetFramework framework)
at WebSupergoo.ABCpdf8.Internal.Spawntaneous.WorkerProcessExe.Init(String filename, Platform platform, TargetFramework framework)
at WebSupergoo.ABCpdf8.Internal.Spawntaneous.WorkerProcessExe..ctor(String filename, Platform platform, TargetFramework framework)
at WebSupergoo.ABCpdf8.Internal.Gecko.GeckoCallback..cctor()
— End of inner exception stack trace —
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.AddUrl(Doc doc, String url, Boolean paged, Int32 width, String& err)
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.AddHtml(Doc doc, String html, Boolean paged, Int32 width, String& err)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html, Boolean paged, Int32 width, Boolean disableCache)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html)

As you can see, the last two exceptions were caused by calling AddImageHtml(), which is the entry point to the invocation of the Gecko rendering engine, therefore my reasoning was that the Gecko engine required something that the MSHTML rendering engine did not need.

I remembered that on IIS, the AppPool’s “Load User Profile” needs to be set, so probably it had something to do with the service running as Local System which does not have a user profile?

As it turns out, setting the service to run under a local user in Control Panel/Administration/Services fixed the problem, and the service was again able to generate PDF files.


VS Solution Dependency Visualizer maintenance release 0.80

April 9, 2012

The new version 0.80 of VS Solution Dependency Visualizer adds one more analysis function: extract all [assembly: ] attributes and values from C# files in all solution projects.

It also fixes a bug that may occur during the analysis of some .csproj project files.

As usual, the latest release of VS Solution Dependency Viewer is available for download here.


Computer says “fakepath”

April 2, 2012

In a web application, we originally implemented the following functionality:

  • a user is allowed to upload files (Word documents, PDFs, etc)
  • if the uploaded file is from a network share (mapped drive), the mapped drive path needs to be translated into a UNC path
  • using the UNC path, a server component can check for changed file dates
  • if a changed file is detected, some workflow should be initiated

Files uploaded with IE (6 and 7) automatically included the path information of the file, whereas for Firefox 3, no file path was passed. This was worked around with a bit of JavaScript:

    ClientScript.RegisterClientScriptBlock(GetType(), "copy",
@"function copyName(){
    document.getElementById('" + edFullFilename.ClientID + "').value = 
        document.getElementById('" + edFilename.ClientID + @"').value;
}", true);
    edFilename.Attributes.Add("onkeyup", "copyName()");
    edFilename.Attributes.Add("onfocus", "copyName()");
    edFilename.Attributes.Add("onchange", "copyName()");

which essentially copied the original file path to a hidden input field.

As browsers are becoming more aware of security, and implement more and more HTML 5 features, all this changes.

IE8 started to introduce the c:\fakepath\ pseudo directory, and other browsers followed. As stated on the WHATWG mailing list,

The original plan was to just have the filename. Unfortunately, it turns out that if you do that, there are certain sites that break, because they expect the path (and they expect a Windows path, no less). This is why Opera and IE8 return a fake path — not because HTML5 says to do it. In fact I made HTML5 say it because they were doing it.

(I would expect Firefox, Safari, and Chrome to follow suit; Firefox for compatibility, and Safari and Chrome for privacy.)

For IE, there remains the solution to add the web server to the Trusted Internet Zone

Additionally, the “Include local directory path when uploading files” URLAction has been set to “Disable” for the Internet Zone. This change prevents leakage of potentially sensitive local file-system information to the Internet.

but we are looking for a generic cross-browser solution.

Probably it’s time to rethink the whole feature and make users copy+paste the file name rather than upload the file for such a scenario.


Windows Update killed my ABCpdf

April 2, 2012

An application I develop (dev environment: Win7 Pro, IE9, ASP.Net 3.5) suddenly showed problems running on Windows Server 2008: the web application could not create PDF documents anymore, and

  • The browser only displayed a “Service Unavailable” message
  • The browser user was automatically logged out of the web application
  • The web request did not show in the IIS log
  • No error in the Application events

A process serving application pool ‘MyAppPool’ suffered a fatal communication error with the Windows Process Activation Service. The process id was ’2688′. The data field contains the error number.

The details tab displayed the following information:

ProcessID 2688
6D000780

Binary data:

In Words

0000: 8007006D

In Bytes

0000: 6D 00 07 80

Teh Internets wanted me to debug IIS and my .Net application with IIS Diagnostics Toolkit, and Debug Diagnostic Tool, and IISState, and awe-inspiring how-to instructions.

Instead, I chose to add logging statements to the code that was affected by the error, and found that cause to be the statement

int theID = doc.AddImageHtml(html);

As it turns out (thank you SO, also here), you need to activate the Gecko engine in ABCpdf to work on (some) IE9 machines (as I said, the code works ok on Win7Pro, and I had recently updated the server):

doc.HtmlOptions.Engine = EngineType.Gecko;

Trying out the Gecko engine, the next result I got was

WebSupergoo.ABCpdf8.Internal.PDFException: ABCpdf cannot detect any printers. Gecko Engine requires a printer installed in the system. Usually, XPS Document Writer would suffice. Try also running the service as an interactive user. —> System.ComponentModel.Win32Exception: The RPC server is unavailable
at System.Drawing.Printing.PrinterSettings.get_InstalledPrinters()
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.get_DefaultPrinter()
— End of inner exception stack trace —
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.get_DefaultPrinter()
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.Preflight(Doc doc)
at WebSupergoo.ABCpdf8.Internal.Gecko.DocAddGecko.AddHtml(Doc doc, String html, Boolean paged, Int32 width, String& err)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html, Boolean paged, Int32 width, Boolean disableCache)
at WebSupergoo.ABCpdf8.Doc.AddImageHtml(String html)

After creating an XPS printer in control panel, the web application was finally able to generate PDF documents again.

According to the ABCpdf support page (6.29), the MSHTML rendering engine should be avoided for future applications:

Unfortunately with the official release of IE9 Microsoft released new documentation which says the IHTMLElementRender::DrawToDC function that was required has been deprecated. This is especially unfortunate given that there is no replacement for this function.

Given that Microsoft appears to be unwilling to support these interfaces we would strongly recommend that on new deployments you consider a move to the new Gecko-based HTML engine available in ABCpdf 8.


Follow

Get every new post delivered to your Inbox.