Mono and ASP.NET MVC v3

by grendel 17. January 2011 09:23
Microsoft has recently released the next version of the ASP.NET MVC stack and many of you have been asking whether Mono supports it. The answer is complicated since this MVC release is much bigger and includes more components than the previous ones. Unfortunately not all of those components have been released as open source software (although they come with the right to redistribute the assemblies) and therefore MVC integration with the Mono sources and build system makes no point at the moment. The highlight of the release is the new Razor template engine which is among the source-less components, so the Mono build of MVC would have to be done without support for Razor, which would probably take away all the fun from using it for many people. Having said that, I have managed to get to compile with current Mono from master after stubbing out several assemblies (read below for the list) and disabling Razor. The code lives in my local git branch and is not going to hit the Mono repository just yet. The reason for this is that we have decided to first make sure that MVC binaries from Microsoft work fine with Mono and only then set out to implement the non-open source assemblies it relies upon. I committed several changes (commits: c372ab7, 0582257, 1d5e3d4, d18d086, aa2ad86, cd511ea and 3e62637) which made it possible to run an MVC v3 application generated from the default VisualStudio template using several assemblies distributed by Microsoft. To get the application working you need to put in your bin/ folder the following dlls:
  • System.Web.Mvc.dll
  • System.Web.Razor.dll
  • System.Web.WebPages.Deployment.dll
  • System.Web.WebPages.dll
  • System.Web.WebPages.Razor.dll
All of the above assemblies, with the exception of the Mvc one, come without sources and will be at some point implemented in Mono. Also note that the Microsoft.Web.Infrastructure assembly added with the above commits is mostly a collection of stubs so you might (and will) come across the NotImplementedException from time to time. So, with the above changes you should be able to give MVC v3 on Mono a spin - if you come across any issues with running your app on Mono, do file a bug report attaching your application (or, if it is too big/private/secret/etc, a small test case which triggers the issue) to help us iron out all the rough edges.

Update: Gonzalo got Razor working under Mono - please report all the issues you have with your Razor applications under Mono! Congrats, Gonzalo :)

Tags: ,

Mono | ASP.NET | MVC | MVC3

ASP.NET 4.0 features: pre-application start methods

by grendel 17. May 2010 13:31

This article applies to Mono version 2.7 (at least SVN revision 157449) or newer

With this post I am starting a series of post on new features found in ASP.NET 4.0 and Mono 2.8. There's no real schedule to the series, I will post new articles as code gets committed to Mono and whenever I have time to develop a sample or two to demonstrate the feature.

ASP.NET 4.0 introduces a feature described by Phil Haack in his recent post. Ability to execute methods before application starts is useful to register BuildProviders without the need to modify web.config - this is its primary purpose, as Phil states in his post, but not the only possible use. Application can take advantage of this feature to provide code to perform early site setup (steps which do not require user feedback or interactivity - creating cache directories, populating freshly installed application with sample content, performing access checks etc) or to load dynamically configurable plugins. After a quick recap of how the feature works, I will describe a simple example of such an application which performs both of the above steps.

To execute code before ASP.NET compiles your site and creates an instance of HttpApplication (or a derived class, if your application has it) you need to take advantage of a new custom attribute introduced in ASP.NET 4.0, PreApplicationStartMethodAttribute. This attribute can be used once per each assembly in your application's bin/ folder. Attribute takes two parameters - type which contains your pre-application start method and a method name. Method must be public static and can take no parameters. Code from sample accompanying this article which demonstrates the usage:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Web;

using PreApplicationStartSupport.Core;

// There can be only one such attribute instance per assembly.
[assembly:PreApplicationStartMethod (typeof (PreApplicationStart.Application.Init), "InitializeApplication")]

namespace PreApplicationStart.Application
{
	public class Init
	{
		public static void InitializeApplication ()
		{
			// perform initialization
		}
	}
}

There are several new 4.0 methods around the ASP.NET class hierarchy which can be called only before or during the pre-application start phase (two of those methods - BuildProvider.RegisterBuildProvider and BuildManager.AddReferencedAssembly are mentioned in Phil's article and pretty well documented on MSDN.

In the sample application, the method shown in the code excerpt above performs all the actions mentioned previously - initial site setup and plugin loading:

public static void InitializeApplication ()
{
	string appDir = HttpRuntime.AppDomainAppPath;
	string stampFile = Path.Combine (appDir, "App_Data", ".setup_done");
	MessageContainer messages = MessageContainer.Instance;

	messages.Clear ();
	if (!File.Exists (stampFile)) {
		messages.Add ("First run - need to perform setup steps.");
		RunSetup (messages, appDir);
	}

	Assembly pluginAssembly = PluginLoader.LoadPlugin ("SamplePlugin");
	if (pluginAssembly == null)
		messages.Add ("Init: failed to load plugin 'SamplePlugin'");
	else
		LoadPluginResources (pluginAssembly, messages);
	}
As you can see, there's nothing at all special to this code - just a regular method. For the links to full source code, see the bottom of this article.

To run the sample application under Mono, you need to have Mono version mentioned at the top of the post and execute this command, in the application's root directory:

MONO_OPTIONS="--debug" xsp4
After browsing to http://localhost:8080/ you will be greeted with the following output (yes, spartan, ugly etc - I know :D):
Event log:

[5/17/2010 6:00:40 PM] First run - need to perform setup steps.'
[5/17/2010 6:00:40 PM] Plugin 'SetupPlugin' loaded successfully.'
[5/17/2010 6:00:40 PM] Init: setup type 'SetupPlugin.Setup' v0.0.0.0 loaded'
[5/17/2010 6:00:40 PM] Setup: creating directories'
	* directory /tmp/PreApplicationStart/PreApplicationStart/App_Data/directory1 created'
	* directory /tmp/PreApplicationStart/PreApplicationStart/App_Data/logs created'
	* directory /tmp/PreApplicationStart/PreApplicationStart/App_Data/backups created'
[5/17/2010 6:00:40 PM] Init: setup successful.'
[5/17/2010 6:00:40 PM] Plugin 'SamplePlugin' loaded successfully.'
[5/17/2010 6:00:40 PM] Init: plugin type 'SamplePlugin.Sample' v0.0.0.0 loaded'
Now stop XSP by pressing ENTER and run it once again. After browsing to the site, you will now see only the following:
Event log:

[5/17/2010 6:01:33 PM] Plugin 'SamplePlugin' loaded successfully.'
[5/17/2010 6:01:33 PM] Init: plugin type 'SamplePlugin.Sample' v0.0.0.0 loaded'
Which means that our pre-application start method has noticed that initial setup had already been performed and there's no need to load the setup plugin and re-run the code.

So, not rocket science but definitely a useful feature. Sample download links:

Tags:

Mono | ASP.NET

A utility to help porting Windows .NET applications to Mono/Unix

by grendel 21. December 2009 08:43

Those of you who work or worked on porting Windows applications (any kind of them - ASP.NET, WinForms, console) might have come across a problem with case-sensitivity of file names. Windows file systems are either case-unaware and case-insensitive (the FAT family) or case-aware but case-insensitive (NTFS). In both cases applications performing I/O don't need to worry about using proper file name casing, as the underlying operating system will ignore the case and open the file without problems. This is not the case on Unix operating systems which have file systems that are both case-aware and case-sensitive.

Mono has had a solution for this problem for a long time - it's called IOMAP. It's a runtime mechanism which, if enabled by exporting the MONO_IOMAP variable, will translate not only file name case to the correct form used on disk, but will also strip DOS drive designators from the paths and translate DOS path separator character to the Unix one, thus making the application work seamlessly. The convenience comes at a price, of course, as translating the file names involves performing a lot of disk I/O to discover the real file name. So if your application is supposed to be ran on Unix most (if not all) of the time then the better to dive into the source and make sure that file names on disk are consistent with their use in the source code.

With small applications the operation is usually straight-forward and takes little time. With bigger codebases, however, it might pose a problem, especially if strings used to access files are constructed in different places of the application as opposed to being just plain literals.

For that reason I have recently created a Mono profiler module (it is somewhat of a misnomer, as the utility has little to do with code profiling) which aims at helping the developer/porter to find places in code which call .NET I/O routines passing them misformed file/directory paths as well as identify places where those strings are constructed. The former part is very simple and it merely prints to the console a stack trace leading to the I/O routine call site every time file/directory name mapping is performed. The latter part, however, is a bit more problematic as it has to deal with two separate moments in string's life - its creation and its actual use. The code uses the Mono profiler API to monitor string allocations storing all the strings created in hash tables as well as remembering the stack frame which leads to the string creation site. The code which does that is pretty fast, so it doesn't impact your application's performance too much even though it collects and stores a large amount of data (mostly pointers and some strings, though). When file name mapping is performed during file I/O, the profiler code looks up the string address and retrieves the stack trace to store it for later use. When the application exits a summary report is printed to the console which includes some statistics on the string, its original (requested) and target (mapped) form as well as a location (if it was possible to determine it) where the string was created. The location is determined using simple heuristics, so it might sometimes point to a location which is near the place where the string was created. The heuristics code walks the stack frames looking for first frame which is in application code - that is it ignores the well-known class library assemblies shipped with Mono (corlib, System*, some Mono* assemblies etc) and all the assemblies installed in the GAC. The first frame which doesn't belong in either of the above is considered to be user's code and is reported to be the string allocation location. If the stack trace doesn't contain any such frames, full trace is recorded and shown in the summary.

To take advantage of this utility you will need Mono from trunk (or 2.8 when it is released). It is advisable to compile your application with full debugging information, so that source files and line numbers can be reported (otherwise only Namespace.Class.Method will be printed). To enable the utility, make sure the MONO_IOMAP environment variable is present and set to 'all', 'case' or 'drive' and execute your application as follows:

   mono --debug --profile=iomap your_application.exe

If you're porting an ASP.NET application, use the following command line:

   MONO_OPTIONS="--debug --profile=iomap" xsp2

Below you can see output from a sample application showing the utility's behavior:

 

$ MONO_IOMAP=all mono --profile=iomap --debug iomap-report-sample.exe 
Running test: Mismatched file name case.
-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-
 - Requested file path: 'files/Test2.txt'
 -     Found file path: 'Files/test2.txt'

-= Stack Trace =-
   at System.Environment.get_StackTrace() in /usr/src/tmp/mono/mcs/class/corlib/System/Environment.cs:line 183
   at System.IO.MonoIO.GetFileAttributes(System.String , MonoIOError ByRef )
   at System.IO.MonoIO.ExistsDirectory(System.String path, MonoIOError ByRef error) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/MonoIO.cs:line 252
   at System.IO.Directory.Exists(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/Directory.cs:line 201
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/FileStream.cs:line 246
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.OpenRead(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 331
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/StreamReader.cs:line 171
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding)
   at System.IO.File.ReadAllText(System.String path, System.Text.Encoding encoding) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 542
   at System.IO.File.ReadAllText(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 537
   at app.MismatchedFileNames() in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 45
   at app.RunTest(System.String banner, System.Action test) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 24
   at app.Main(System.String[] args) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 39

-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-
 - Requested file path: '/home/grendel/Projects/work/iomap-report-sample/files'
 -     Found file path: '/home/grendel/Projects/work/iomap-report-sample/Files'

-= Stack Trace =-
   at System.Environment.get_StackTrace() in /usr/src/tmp/mono/mcs/class/corlib/System/Environment.cs:line 183
   at System.IO.MonoIO.GetFileAttributes(System.String , MonoIOError ByRef )
   at System.IO.MonoIO.ExistsDirectory(System.String path, MonoIOError ByRef error) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/MonoIO.cs:line 252
   at System.IO.Directory.Exists(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/Directory.cs:line 201
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/FileStream.cs:line 270
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.OpenRead(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 331
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/StreamReader.cs:line 171
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding)
   at System.IO.File.ReadAllText(System.String path, System.Text.Encoding encoding) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 542
   at System.IO.File.ReadAllText(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 537
   at app.MismatchedFileNames() in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 45
   at app.RunTest(System.String banner, System.Action test) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 24
   at app.Main(System.String[] args) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 39

File 1: Hello, world - test2.txt here


-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-
 - Requested file path: 'files/test.txt'
 -     Found file path: 'Files/TesT.txt'

-= Stack Trace =-
   at System.Environment.get_StackTrace() in /usr/src/tmp/mono/mcs/class/corlib/System/Environment.cs:line 183
   at System.IO.MonoIO.GetFileAttributes(System.String , MonoIOError ByRef )
   at System.IO.MonoIO.ExistsDirectory(System.String path, MonoIOError ByRef error) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/MonoIO.cs:line 252
   at System.IO.Directory.Exists(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/Directory.cs:line 201
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/FileStream.cs:line 246
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.OpenRead(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 331
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/StreamReader.cs:line 171
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding)
   at System.IO.File.ReadAllText(System.String path, System.Text.Encoding encoding) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 542
   at System.IO.File.ReadAllText(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 537
   at app.MismatchedFileNames() in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 46
   at app.RunTest(System.String banner, System.Action test) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 24
   at app.Main(System.String[] args) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 39

File 2: Hello, world - TesT here


Running test: Access from an assembly.
Reading a file.
-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-
 - Requested file path: 'fileS/tESt.txt'
 -     Found file path: 'Files/TesT.txt'

-= Stack Trace =-
   at System.Environment.get_StackTrace() in /usr/src/tmp/mono/mcs/class/corlib/System/Environment.cs:line 183
   at System.IO.MonoIO.GetFileAttributes(System.String , MonoIOError ByRef )
   at System.IO.MonoIO.ExistsDirectory(System.String path, MonoIOError ByRef error) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/MonoIO.cs:line 252
   at System.IO.Directory.Exists(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/Directory.cs:line 201
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/FileStream.cs:line 246
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.OpenRead(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 331
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/StreamReader.cs:line 171
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding)
   at System.IO.File.ReadAllText(System.String path, System.Text.Encoding encoding) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 542
   at System.IO.File.ReadAllText(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 537
   at LibClass.ReadFile(System.String dir, System.String fileName) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-lib.cs:line 12
   at app.AccessFromAssembly() in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 51
   at app.RunTest(System.String banner, System.Action test) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 24
   at app.Main(System.String[] args) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 40

-=-=-=-=-=-=- MONO_IOMAP REPORT -=-=-=-=-=-=-
 - Requested file path: '/home/grendel/Projects/work/iomap-report-sample/fileS'
 -     Found file path: '/home/grendel/Projects/work/iomap-report-sample/Files'

-= Stack Trace =-
   at System.Environment.get_StackTrace() in /usr/src/tmp/mono/mcs/class/corlib/System/Environment.cs:line 183
   at System.IO.MonoIO.GetFileAttributes(System.String , MonoIOError ByRef )
   at System.IO.MonoIO.ExistsDirectory(System.String path, MonoIOError ByRef error) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/MonoIO.cs:line 252
   at System.IO.Directory.Exists(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/Directory.cs:line 201
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/FileStream.cs:line 270
   at System.IO.FileStream..ctor(System.String path, FileMode mode, FileAccess access, FileShare share)
   at System.IO.File.OpenRead(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 331
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/StreamReader.cs:line 171
   at System.IO.StreamReader..ctor(System.String path, System.Text.Encoding encoding)
   at System.IO.File.ReadAllText(System.String path, System.Text.Encoding encoding) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 542
   at System.IO.File.ReadAllText(System.String path) in /usr/src/tmp/mono/mcs/class/corlib/System.IO/File.cs:line 537
   at LibClass.ReadFile(System.String dir, System.String fileName) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-lib.cs:line 12
   at app.AccessFromAssembly() in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 51
   at app.RunTest(System.String banner, System.Action test) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 24
   at app.Main(System.String[] args) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:line 40

Reading file: Hello, world - TesT here



-=-=-=-=-=-=-= MONO_IOMAP Stats -=-=-=-=-=-=-=
    Count: 2
Requested: /home/grendel/Projects/work/iomap-report-sample/fileS
   Actual: /home/grendel/Projects/work/iomap-report-sample/Files
Locations:
        LibClass:ReadFile (string,string) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-lib.cs:12

    Count: 7
Requested: fileS/tESt.txt
   Actual: Files/TesT.txt
Locations:
        LibClass:ReadFile (string,string) in /home/grendel/Projects/work/iomap-report-sample/iomap-report-lib.cs:12

    Count: 4
Requested: /home/grendel/Projects/work/iomap-report-sample/files
   Actual: /home/grendel/Projects/work/iomap-report-sample/Files
Locations:
        app:MismatchedFileNames () in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:45
        app:MismatchedFileNames () in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:45

    Count: 7
Requested: files/Test2.txt
   Actual: Files/test2.txt
Locations:
        app:MismatchedFileNames () in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:45

    Count: 7
Requested: files/test.txt
   Actual: Files/TesT.txt
Locations:
        app:MismatchedFileNames () in /home/grendel/Projects/work/iomap-report-sample/iomap-report-sample.cs:46

 

Tags:

Mono | ASP.NET | Tip

The importance of micro optimizations

by grendel 7. October 2009 07:54

Many years ago when coding in C/C++ or assembler I learned one important rule - DO micro-optimize your code. Unlike macro scale optimization, which should be done after your code is generally working and feature complete, micro optimization should be applied as you go. Why? Because sticking to a few simple rules (which don't affect the overall way in which the code works) as you code is easier than having to fish for the small inefficiencies in your source later on. Some of the optimizations can be performed by the compiler (like taking a constant expression out of loop) but some still depend on the coder doing the Right Thing™.

It seems that, a month ago, I proved that I had forgotten the rules I learned and applied in the past by committing code which affected Mono ASP.NET's performance in a really major way. I'm posting this blog entry as a memento to myself and to others who, like me, might have forgotten that such rules still apply in the 21st century despite all our smart compilers, sophisticated virtual machines and runtimes.

The committed code did (eventually) something like this:

void Method (string s) 
{
	string s2 = GetSomeString ()

	CallMethod (s + '@' + s2);
}

The CallMethod line caused (as we discovered yesterday) a drammatic decrease in MojoPortal's performance - its home page would open on my computer, using local connection in 2.56s (yes, seconds) on average. Gonzalo went hunting for the issue and this morning I read a mail from him pin-pointing (after lots and lots of time wasted on it) the performance loss to that very line. I fixed the issue in our code and suddenly the load time for the page went down to ~107ms (yes, milliseconds)! One might ask whether it was because concatenating two strings and a char is so inefficient? Yes, it is inefficient (as the operation has to convert a char to a string, then allocate and concatenate two more strings) but not enough to justify such performance loss. The key here is the frequency at which the code is being called - GetSection gets called hundreds (if not thousands) of times during one request, and the operation's summed up time contributes to the peformance loss.

This is exactly what I described at the beginning of this post - a micro optimization, a right thing to do in the right place. It can be compared to adding two integer or float variables whose values don't change in a loop which takes thousands of iterations and is invoked by some code very often. The addition is not very expensive if your code performs it once in a while, but in the scenario when it repeatedly calls the code, you will see major loss of performance. The solution is to take the operation outside of the loop. This is precisely what happened here.

So, even in this day and age - ladies and gentlemen, do micro-optimize and pay attention to where your "slightly inefficient" code lives :)

Update:

As Jon suggested below, I should have suggested possible solutions to the issue:

  • CallMethod could take 3 arguments instead of one (depends on what the method does, of course) - that was the solution chosen in this instance to fix the issue as the string concatenation wasn't at all necessary.
  • Using "@" instead of '@' would avoid costly (culture-sensitive) conversion of a char to string

Update:

I'm officially an idiot. The real cause of the slowness was that we've been bypassing the cache because of different key being generated to query cache and another to insert into it. My only excuse - lack of sleep :P (I know, a poor one :D). The post's main point still stands, though - just ignore the rambling about string concatenation. Now, where's my brown paper bag...

Tags:

ASP.NET | General | Mono

Umbraco on Mono

by grendel 23. July 2009 12:34

This week I've spent some time trying to fix bugs preventing Umbraco from running unmodified on Mono. Unfortunately, some modifications to Umbraco source code (diff here) were necessary due to the inherent Windows-centric coding in parts of the CMS. The diff above takes care of just one part of the cross-platform issues in Umbraco, namely it replaces hard-coded use of the \ directory separator character with the, correct, Path.DirectorySeparatorChar. For some reason MONO_IOMAP mechanism didn't take care of the problem, hence the workaround (I'm going to fix the IOMAP issue after I'm back from vacation mid-August). The other cross-platform issue is (very common in Windows .NET applications) inconsistent use of file naming case which MONO_IOMAP is dealing with nicely.

Making Umbraco run on Mono required some fixes to our ASP.NET parser (revisions 138474, 138520, 138521 and 138592 in trunk, for those interested. Backported to both 2.4 and 2.4.2 branches) as well as making AppDomain not reload when Umbraco writes to the Web.config file. All of the fixes willbe part of the upcoming 2.4.2.3 release. There are however some issues not fixed yet, but with workarounds:

Web.config modifications during setup
Generally it works fine - application isn't restarted, data is written to the config file, but for some reason Umbraco installer caches data from before database access credentials change. So if, for instance, you were presented with MS-SQL setup box and switched to MySQL, the connection and/or database creation will fail trying to use MS-SQL code. This is a bug in Mono which I'm going to investigate in August. Workaround for this issue is to stop the app after selecting the new database provider and credentials and just restart the installation process - everything will work fine from that moment on.
VistaDB not working
VistaDB comes without source, so I wasn't able to investigate what the issue was. The symptom is that when trying to connect to or populate VistaDB database you will get either a ThreadAbortException, a TypeInitializationException or a JIT error about invalid IL in the VistaDB assembly. This is likely a runtime issue - to be investigated. You can use either SQL Server or MySQL providers instead (I used MySQL for testing).
Intermittent exceptions
Sometimes visiting certain pages in the admin interface causes an exception to be thrown (usually NullReferenceException) but I never succeeded in reproducing the exception(s) by repeating the actions. To be investigated.
TinyDLL.dll is a native library
Since it is native, our compiler cannot figure out what to do with it and throws an exception. Simply remove the dll from there - it shouldn't be there anyway.
So, this is the current status of Umbraco on Mono - please play with it and report any bugs you might find, giving exact directions how to reproduce the issue. I will take a look at them once I'm back from vacations.

Since not everybody might want to or know how to compile and set up Umbraco from sources, I have precompiled the package with the diff applied and repackaged the 4.0.2.1 distribution with the bin/ assemblies replaced by their "Mono versions" (note that the ONLY difference in the assemblies is that the patched ones are cross-platform). Here are the download locations:

Tags:

Mono | ASP.NET

RecentComments

Comment RSS