Tracking down ScrewTurn Wiki

Back in 2012, the world was shocked by news that ScrewTurn Wiki would not be developed any further.

The universe of .Net-based open-source wikis was small then, and it is even smaller today.

Time to find out what happened to the original STW source code. I tracked down a couple of versions of the original STW3, as well as successors:

Compilation and initial startup showed the following results:

 

Version .Net Version VS Version License compiled run test
3.0.5.610 3.5 12 (2013) GPL V2 VS 2013 ok
3.0.5.629 3.5 11 (2010) GPL V2 VS 2013 ok
3.0.6.630 4.5 12 (2013) GPL V2 VS 2013 fails to load sql page provider
3.1.0.0 4.7.2 15 (2017) GPL V2 VS 2017 fails to load sql provider
4.0.5.145 4.0 11 (2010) GPL V2 VS 2017 with Azure SDK ok
6.0.0.0 4.6 14 (2015) GPL V3 VS 2017 with Azure SDK ok (run as site)

Comments:

  • VS Version is the version of Visual Studio the original code was developed with, while compiled shows the Visual Studio version I used to compile the code.
  • Versions 3.0.6.630 and 3.1.0.0 compiled and ran, but did not load the SQL Providers.
  • Versions 4 and above reference Azure libraries. Since the Azure SDK from Visual Studio 2010 is not available anymore, I used Visual Studio 2017 with its Azure SDK.
  • Version 6 is an ASP.Net MVC application. All previous versions are ASP.Net.
  • Version 6 requires ScrewTurn Wiki to run as an IIS site due to lack of application-relative paths in code and markup. All previous versions also run as IIS applications inside an IIS site.

VS Solution Dependency Visualizer supports .NET Core Project Files

When I tried to analyze an unknown Visual Studio solution in my tool VS Solution Dependency Visualizer, I noticed that the project dependencies were not analyzed correctly. In fact, the dependencies were completely ignored.

A bit of research turned up that the solution in question was partly migrated to .NET Core, and the .csproj project files have a slightly different structure than good old .Net project files.

First, the classic .Net project files use a namespace

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

whereas .NET Core project files come without XML namespace, and have the root attribute Sdk:

<Project Sdk="Microsoft.NET.Sdk">

Also, files are not compiled if they are explicitly contained in the .csproj file, but rather by a rule-based Default Compilation mechanism which decides whether files in or below the project directory are compiled, embedded, or ignored.

For a detailed documentation of .NET Core project files, see MS docs or the dotnet GitHub docs.

The latest version of VS Solution Dependency Visualizer with support for .NET Core projects is available for download here.

BTW: In previous versions of this tool, I tried to register VS Solution Dependency Visualizer as an External Tool in all installed versions of Visual Studio.

However, things change all the time, and VS 2013 was the first version that did not support registration via registry, and with VS 2017 things got even more different, and finally I had to give up.

Instructions on how to add VS Solution Dependency Visualizer as an External Tool in your version of Visual Studio can be found in the readme file that comes with the installer.

Enabling CORS in IIS7 Web Application

Cross-origin HTTP request are restricted to (from MDN)

  • The only allowed methods are:
    • GET
    • HEAD
    • POST
  • The only allowed values for the Content-Type header are:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

I needed to grant a local web application the ability to POST JSON-formatted data, which is clearly beyond the Content-Type restrictions above.

Without modifying the application, I noticed OPTION requests instead of POST requests to the local URL.

As it turns out, an OPTIONS request is sent before a cross-origin POST (if it is not a “simple” request) to check whether the webserver allows a POST, given origin, method, content type, and HTTP headers:

Preflighted requests

Unlike simple requests (discussed above), “preflighted” requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send.  Cross-site requests are preflighted like this since they may have implications to user data.

Fortunately, a single thread on SO helped me enable CORS but simply editing the application’s web.config file. The <system.webServer> section needs to contain the following lines:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule"/>
  </modules>
  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Methods" value="GET,POST,OPTIONS" />
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

The <remove> entry disables the standard handler (WebDAV) for the OPTIONS verb . The <customHeaders> section contains all allowed HTTP header (Access-Control-Allow-*), and Access-Control-Allow-Headers specifies that JSON is recognized as valid Content-Type.

Unknown Class “ProfileCommon”

I was asked to have a look at a Web Site project, and whether it could be converted into a Web Application project. So I created a web application project, copied and added all project files, and after a bit of editing (adding namespaces, etc.) I came across the error:

The type or namespace name ‘ProfileCommon’ could not be found (are you missing a using directive or an assembly reference?)

I soon found out that the class ProfileCommon is being automatically generated during the build process of a Web Site project, and in the meantime a VS BuildTask and a couple lines of code can be found on the internetz which replicate to original code generator.

To understand what it going on, I opened the original web site project, searched for a reference to ProfileCommon, and hit Go To Definition to retrieve the generated code.

As it turns out, the generator simply parses the configuration/system.web/profile/properties section of the web site’s web.config file.

Every item in the section contains a property definition in the form

<add name="[PropertyName]" defaultValue="[value]" 
    type="[.Net Datatype]" />

From this information, the generator creates a public class like this:

using System;
using System.Web;
using System.Web.Profile;
public class ProfileCommon : System.Web.Profile.ProfileBase {

Each declared property is generated as

  public virtual [Datatype] [PropertyName] {
    get {
      return (([Datatype)(this.GetPropertyValue("[PropertyName]")));
    }
    set {
      this.SetPropertyValue("[PropertyName]", value);
    }
  }

and finally

  public virtual ProfileCommon GetProfile(string username) {
    return ((ProfileCommon)(ProfileBase.Create(username)));
  }
}

My guess is that the ProfileBase.GetPropertyValue() method also evaluates the defaultValue clause somehow, but MSDN does not mention this question.

Stumbling Upon the “Not Pre-Compiled” Error Message

I maintain an ASP.Net application, and I recently had to add a couple of new features. Development started with .Net 1.1, then 2.0, then 4.0. Since we were already on .Net 4, the new features are implemented in MVC3.

Everything worked fine until I wanted to deploy the ASP.Net-plus-MVC application.

I hit Publish, zipped the result, and unzip everything into its usual directory.

But…

The file ‘/somepage.aspx’ has not been pre-compiled, and cannot be requested.

or

Die Datei /somepage.aspx wurde nicht vorkompiliert und kann nicht angefordert werden.

I asked the internetz, and they suggested checking referenced assemblies. So I installed the MVC3 setup (on IIS8), just in case. But that did not resolve my problem.

While browsing thru tons of useless (and sometimes wrong) tips and tricks, I found this answer on SO:

I got this error when I upgraded a site from 2.0 to 4.0. The error was caused by a file PrecompiledApp.config in the site’s root directory. Once I deleted that file, the site started working.

Then it struck me: I had deployed the previous version of the web application using my build process which also compiles (and merges) .aspx files.

I have not yet found the time to adapt this build process to also support MVC projects (or test it whether it already does!), so I deployed what Publish produces. And this is not pre-compiled pages.

So when I overwrote the old version with the new version, the file PrecompiledApp.config remained.

After deleting the file, the web application started up again.

Adding MVC 3 to an existing ASP.Net 4 Web Application

If you maintain an ASP.Net application for some time (as I do 😉 ), and also gained experience in developing using ASP.Net MVC, you probably want to implement any new features in MVC.

So I search for ways to add MVC to an existing ASP.Net application, and found a couple of solutions. However, they refer to older versions of MVC, so I’ll sketch here which steps are required for ASP.Net 4 and MVC 3.

The original information comes from these articles:

For some changes and additions, it’s easier if you already have an MVC project. If you do not, simply create a new “MVC3 Web Application” – all the required files will be generated.

Add assemblies

In your project, add references to the following assemblies:

  • System.Web.Abstractions
  • System.Web.Mvc
  • System.Web.Routing

Unload project and edit project

Under <ProjectTypeGuids>, add the Guid “{E53F8FEA-EAE0-44A6-8774-FFD645390401};” to get MVC-specific “Add…” items in the context menu of the Solution Explorer, such as Add Controller and Add View. The line then reads

 <ProjectTypeGuids>{E53F8FEA-EAE0-44A6-8774-FFD645390401};
    {349c5851-65df-11da-9384-00065b846f21};
    {fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

In the first <PropertyGroup>, add the line

<MvcBuildViews>true</MvcBuildViews>

Add the end of the project file, add the MVC build target:

<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
  <AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>

Edit web.config

Under <system.web>/<compilation>, add the following assembly references

<assemblies>
  <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>

Under <system.web>/<pages>, add the following namespace declarations

<namespaces>
  <add namespace="System.Web.Helpers" />
  <add namespace="System.Web.Mvc" />
  <add namespace="System.Web.Mvc.Ajax" />
  <add namespace="System.Web.Mvc.Html" />
  <add namespace="System.Web.Routing" />
  <add namespace="System.Web.WebPages"/>
</namespaces>

Probably you also need to tweak assembly bindings

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
      <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Project additions

In the project, add the folders Controllers and Views, and Views/Shared.

Copy the Views/web.config file from the other MVC application.

Edit Global.asax.cs

Add these using statements:

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

Add these lines to Application_Start():

AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);

If you want to use the MVC Razor engine, also add:

ViewEngines.Engines.Add(new RazorViewEngine());

Add the methods RegisterRoutes() and RegisterGlobalFilters() as:

protected static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
  routes.MapRoute("Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = "" }
  );
}

protected static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
  filters.Add(new HandleErrorAttribute());
}

Personally, I prefer to have each action in a separate controller class, so I use the solution presented here:

routes.MapActionRoute(
  "Default",
  "{controller}/{action}/{oid}",
  new { controller = "Default", action = "Index", oid = UrlParameter.Optional },
  new object { }
);

Start Page

If you have a defined Start Page in the ASP.Net project, you will need to redirect there from the default MVC action:

namespace MyMvcWeb.Controllers
{
  public class DefaultController : Controller
  {
    public ActionResult Index()
    {
      return Redirect(Url.Content("~/default.aspx"));
    }
  }
}

Mixing Razor and WebForms views

Razor and WebForms are two different view engines, and cannot be mixed.*

If you want to use an ASP.Net master page for a WebForms MVC view, have a look at this SO answer.

As for WebForms, the ASP.Net master page derives from System.Web.UI.MasterPage, whereas an MVC master page derives from System.Web.Mvc.ViewMasterPage (which itself also derives from System.Web.UI.MasterPage).

As for Razor, master pages have been replaced by layout views. Both views and layout views derive from System.Web.Mvc.WebViewPage<T> which has no common base class with MasterPage.

*) Note that there is a solution to using Razor views in ASP.Net masterpages (Hanselman, Hawley, Baloch), but it looks a bit hacky and fragile – and, of course, unsupported 😉

Debugging Routes

If things do not turn out as expected after implementing all these changes, have a look at the Route Debugger (original post, updated version).

Using Linked Files in ASP.Net Applications

One of the web applications I maintain still runs on ASP.Net 3.5. A specific feature required that there also be a .Net 4 version of the application, so I thought about how to share the source code for both .Net versions.

Essentially, this can be achieved by creating a new solution and new projects and having the projects link to the original source files. That’s ok so far and it works fine.

However, when it comes to the ASP.Net application itself, things do not work straight forward:

My build process invokes both aspnet_compiler and aspnet_merge where aspnet_compiler requires all markup files to be present in the project directory, as it seems to ignore the contents of the .csproj file.

So how do I get the markup files into the linking project without manually copying the files? The process should be performed automatically, so that any changes in the original source are propagated to the linking project.

Luckily I found this article Using Linked Files in ASP.Net Applications, which unfortunately has been removed recently. In principle, you need to specify new build targets that invoke the file copy for the linked files. While the original solution only covered copying “content” files, I extended it to also copy “compile” files:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="CopyLinkedContentFiles">
    <Copy Condition=" '%(Content.Link)' != '' " 
                SourceFiles="%(Content.Identity)" 
                DestinationFiles="$(OutputPath)\..\%(Content.Link)" 
                SkipUnchangedFiles="true" />
    <Message Text="Copying linked file %(Content.Link)" />
  </Target>
  <Target Name="CopyLinkedCompileFiles">
    <Copy Condition=" '%(Compile.Link)' != '' " 
                SourceFiles="%(Compile.Identity)" 
                DestinationFiles="$(OutputPath)\..\%(Compile.Link)" 
                SkipUnchangedFiles="true" />
    <Message Text="Copying linked file %(Compile.Link)" />
  </Target>
  <PropertyGroup>
    <BuildDependsOn>
      CopyLinkedContentFiles;
      CopyLinkedCompileFiles;
      $(BuildDependsOn);
    </BuildDependsOn>
  </PropertyGroup>

Save the file as CopyLinks.targets in your project directory. Then edit the .csproj file to add the line

  <Import Project="$(MSBuildProjectDirectory)\CopyLinks.targets" />

(find other <Import> sections and insert there).

As you build your  project, the linked files will now be copied to the respective location in the project linked the original files.

The complete targets file can be downloaded here.

Workaround for “Could not load control AjaxControlToolkit”

I wanted to update an ASP.Net application I maintain to the latest version of Ajax Control Toolkit, only to get an exception raised by the toolkit

Could not load control AjaxControlToolkit.[ControlName]. The script reference(s) of this control was not loaded correctly. If AjaxControlToolkit.config is used, probably this control is not registered properly.

See also posts on SO such as here and here.

The internets suggest not to use the latest version, as not versions are created equal, and point to the January 2013 edition as reliable.

Using .resx Strings in Javascript

If you output a string in ASP.Net MVC using @ (Razor) or <%: %> (aspx), the string will automatically get HTML-encoded.

So if you reference a resource string (.resx) in Javascript,

<script type="text/javascript">
var x = "@resx.StringId";
</script>

AND the string contains characters the HTML-encoded (such as “&” => “&amp;”, umlauts, accented characters, etc.), you’ll end up with a lot of ampersands.

The way I solved this problem in a recent project was to define an HtmlHelper extension like this:

public static IHtmlString JsString<TModel>(this HtmlHelper<TModel> html, string s)
{
  return html.Raw(HttpUtility.JavaScriptStringEncode(s));
}

The string is first Javascript-encoded, and then written to the response stream unchanged using HtmlHelper.Raw().

Name Changes for CKEditor Toolbar Items

Upgrading one of my web applications from FCKeditor 2 to CKEditor 3, I noticed that the names for some toolbar items have changed.

Comparing the toolbar description for FCK2 and CK3, the name changes are:

FCKeditor 2 CKEditor 3
PasteWord PasteFromWord
StrikeThrough Strike
OrderedList NumberedList
UnorderedList BulletedList
JustifyFull JustifyBlock
Rule HorizontalRule
Style Styles
FontFormat Format
FontName Font