Tuesday, 16 July 2013

Non-obvious behaviour in the C# compiler with params array

I recently changed a function I was using inside a test, to take a params string[] parameter instead of a plain string[].

The change of behaviour surprised me at first, although after tracing the exception I could see why it happened.

Here’s an example (inside a test class, using NUnit and FluentAssertions)


private static int HowManyStrings(params string[] y)
{
return y.Length;
}

[Test]
public void TestHowManyStrings()
{
HowManyStrings("foo", "bar").Should().Be(2);
HowManyStrings("foo").Should().Be(1);

string myNullString = null;
HowManyStrings(myNullString).Should().Be(1);
HowManyStrings(default(string)).Should().Be(1);
HowManyStrings(null).Should().Be(1);
}


The last test causes an exception, because it’s OK to pass a string[] as the parameter, and the null (of unknown type) matches the string[] better than making a string[] { null } out of it.



It does feel like passing an untyped null to a params array parameter should produce a warning, either in the compiler or something like ReSharper – but neither flagged it up (C#5, ReSharper 7).

The Wildhearts Fishing For Luckies lenticular CD cover

Apropos of nothing…

My copy of “Fishing For Luckies” by The Wildhearts has a lenticular cover that flicks between two pictures. I decided to make an animated gif that alternates between the two to use as the cover art when I ripped it. Whether anything renders the animation is a different matter, but just so I never have to do it again, I’m recording the image here.

Fishing

I scanned the inlay at 300dpi and then wrote some code to split the image into odd and even sets of (3-line-high) blocks, repeating each row twice. I then saved out the two images formed from the odd and even sets and used UnFREEz to make the animated gif.

Anyway, it’s a good album and you can buy it with the lenticular cover, or with a normal cover but with bonus disc, or ready-ripped but with some static cover art .

Thursday, 11 July 2013

Checkboxes Beside Labels in ASP.Net MVC4

This had me stumped for a while, so here’s what you need to do:

Inside your form,

<li>
@Html.CheckBoxFor(m => m.XX)
@Html.LabelFor(m => m.XX, new {@class="checkbox"})
</li>


This is for a boolean XX in the model, marked with the appropriate attributes to get the label text. The important thing is the class attribute for the label, because that applies the style defined in the default css:



label.checkbox { display: inline;}



To make it appear on the same line as the checkbox itself.

Tuesday, 14 May 2013

XPS Observations

All these observations were made printing an XPS document which seems to have a problem with the embedded subsetted font. The printer was a non-XPS printer (the Xerox Phaser 8560) but the problems affected the Microsoft PostScript driver as much as the Xerox driver.

Code to test printing XPS using the native API can be found on Logos Bible Software Code Blog

If your text looks blurry / fuzzy in print, it could be because the print path has converted your text to bitmaps.

The font problem which hadn’t upset .net 3.5 caused .net 4.5 to render each word rasterized as an individual png.

Try printing to the Microsoft Xps Document Writer and looking inside the XPS document (below).

.net 4.5 is more fussy than .net 3.5

Compiling the same code for a different target framework can change the results you’ll get.

The native API (with raw spooling) is fussier still

A problematic XPS document could cause reams of pages with random junk at the top, or event have documents be reordered in the print queue.

The native API (with metafile spooling) behaves unpredictably with bad input

You might get pages of junk, you might get a complete document, you might get nothing at all.

The XPS format is pretty easy to mess with manually, once you’ve generated your document

  • Make a test harness that can print the XPS document, using

LocalPrintServer.GetDefaultPrintQueue().AddJob("PrintJob", file, false);

or the native code referenced above.

  • Extract the files from the XPS package by treating it as a zip (I used 7zip to extract the contents into a folder).

  • Have a look around the extracted Resource folders for images (.png) and embedded obfuscated fonts (.odttf).

  • Document text and references to the resources can be changed by editing the xml format file.

  • Select all the files and subfolders in the top level of the extracted package, and add them to a zip file named like “MyHackedUpDocument.xps” (i.e. use the zip format, but .xps extension).

  • You’ve got a new XPS document to try printing.

The obfuscated fonts can be deobfuscated if you want to see what they were originally, using code like this StackOverflow answer but be aware that they will usually have had any unused glyphs stripped out (subsetted).

XPS Packages can contain multiple documents with different (per-document) print tickets

But the .net API ignores them.

You need to feed the tickets to the print job’s document writer.

  • Pick the tickets out of the fixed documents using its FixedDocumentSequenceReader.

  • Create a writer with System.Printing.PrintQueue.CreateXpsDocumentWriter(queue) and handle WritingPrintTicketRequired to supply the ticket based on CurrentPrintTicketLevel and Sequence (which is 1-based).

  • Use MergeAndValidatePrintTicket with the queue’s DefaultPrintTicket to get a valid ticket with the settings for that document.

Monday, 22 April 2013

Building a wildcard containing asterisks in an MSBuild CreateItem

Suppose you want to use CreateItem to build a string containing a pattern, based on the value of a property being passed to your Target (say, to make an OpenCover Filter group).
If the Include attribute value includes an asterisk (even if escaped as hex) then a pattern match is attempted against the filesystem, and you end up with nothing in the group.
After several hours of messing about, I came up with a solution building the pattern with the word STAR instead of an asterisk, and using an inline C# Target to create a TaskItem with the correct text.
Here is the result, in a .proj file at the solution level and using the NuGet packages for OpenCover, NUnit.Runners and ReportGenerator.
<?xml version="1.0" encoding="utf-8"?>

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

    <PropertyGroup>

        <OpenCoverVersion>4.5.1403</OpenCoverVersion>

        <NUnitVersion>2.6.2</NUnitVersion>

        <ReportGeneratorVersion>1.8.0.0</ReportGeneratorVersion>

    </PropertyGroup>

 

    <ItemGroup>

        <TestedNamespace Include="MyCode">

            <TestsNamespace>MyCode.Tests</TestsNamespace>

        </TestedNamespace>

        <TestedNamespace Include="MyWebStuff">

            <TestsNamespace>MyWebStuff.Tests</TestsNamespace>

        </TestedNamespace>

        <ReportFiles Include="@(TestedNamespace->'OpenCover\%(TestsNamespace).opencover.xml')"/>

    </ItemGroup>



    <PropertyGroup>

        <Configuration Condition="'$(Configuration)' == ''">Release</Configuration>

        <OpenCoverPath>$(MSBuildProjectDirectory)\packages\OpenCover.$(OpenCoverVersion)</OpenCoverPath>

        <OpenCoverMSBuildTasksPath>$(OpenCoverPath)\MSBuild</OpenCoverMSBuildTasksPath>

        <OpenCoverPath>$(MSBuildProjectDirectory)\packages\OpenCover.$(OpenCoverVersion)</OpenCoverPath>

        <OpenCoverMSBuildTasksPath>$(OpenCoverPath)\MSBuild</OpenCoverMSBuildTasksPath>

        <NUnitRunner>$(MSBuildProjectDirectory)\packages\NUnit.Runners.$(NUnitVersion)\tools\nunit-console-x86.exe</NUnitRunner>

        <ReportGenerator>$(MSBuildProjectDirectory)\packages\ReportGenerator.$(ReportGeneratorVersion)\ReportGenerator.exe</ReportGenerator>

    </PropertyGroup>

 

    <Import Project="$(OpenCoverMSBuildTasksPath)\OpenCover.targets"/>

    <UsingTask AssemblyFile="$(ReportGenerator)" TaskName="ReportGenerator"/>

 

    <UsingTask TaskName="TurnSTARintoActualStar" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">

        <ParameterGroup>

            <Input ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />

            <Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />

        </ParameterGroup>

        <Task>

            <Code Type="Fragment" Language="cs">

                <![CDATA[

    if (Input.Length > 0)

    {

        Result = new TaskItem[Input.Length];

        for (int i = 0; i < Input.Length; i++)

        {

            ITaskItem item = Input[i];

            Result[i] = new TaskItem(item.GetMetadata("Identity").Replace("STAR", "*"));

        }

    }

]]>

            </Code>

        </Task>

    </UsingTask>

 

    <Target Name="Clean">

        <RemoveDir Directories="OpenCover" />

        <CallTarget Targets="Report"/>

    </Target>

 

    <Target Name="Report" DependsOnTargets="Build">

        <ReportGenerator

            TargetDirectory="OpenCover\Report"

            ReportTypes="Html"

            ReportFiles="@(ReportFiles)"

            />

    </Target>

    

    <Target Name="Build" Inputs="@(TestedNamespace)" Outputs="OpenCover\%(TestsNamespace).opencover.xml">

        <MakeDir Directories="OpenCover" />

 

        <CreateItem Include="+[%(TestedNamespace.Identity)STAR]STAR;-[%(TestedNamespace.TestsNamespace)]STAR;-[%(TestedNamespace.Identity)STAR]%(TestedNamespace.Identity).Properties.STAR">

            <Output TaskParameter="Include" ItemName="SafeFilters" />

        </CreateItem>

        

        <CreateItem Include="$(MSBuildProjectDirectory)\%(TestedNamespace.TestsNamespace)\bin\$(Configuration)\%(TestedNamespace.TestsNamespace).dll">

            <Output TaskParameter="Include" PropertyName="TestsAssemblyPath" />

        </CreateItem>

 

        <CreateItem Include='"$(TestsAssemblyPath)" /noshadow /xml:"OpenCover\%(TestedNamespace.TestsNamespace).nunit.xml"'>

            <Output TaskParameter="Include" PropertyName="NUnitArgs" />

        </CreateItem>

 

        <CreateItem Include="@(TestedNamespace->'OpenCover\%(TestsNamespace).opencover.xml')">

            <Output TaskParameter="Include" PropertyName="OpenCoverXml" />

        </CreateItem>

 

        <TurnSTARintoActualStar Input="@(SafeFilters)">

            <Output ItemName="Filters" TaskParameter="Result" />

        </TurnSTARintoActualStar>

 

        <OpenCover 

            ToolPath="$(OpenCoverPath)"

            Register="True" 

            Target="$(NUnitRunner)" 

            TargetArgs='$(NUnitArgs)'

            Output="$(OpenCoverXml)"

            CoverByTest="$(TestsAssemblyPath)"

            Filter="@(Filters)"/>

    </Target>

 

</Project>


Thursday, 21 March 2013

Ubuntu 12.10 Console Mode under Virtual PC

Getting this running turned out to be a bit of a challenge. The information is out there, but spread all over the place, so I’m gathering it here.

  • Download the 12.10 network install (mini.iso) for i386 from here.
  • Create a virtual machine with a >5GB dynamic hard disk and with the virtual DVD drive pointed at your downloaded mini.iso
  • Select “Install” and press tab to edit the boot options
  • Add the text ata_piix.prefer_ms_hyperv=0 before the word quiet, then press Enter. Without this the installer won’t see the virtual hard disk to install to.
  • Go through the installer, and choose to install ssh-server, because if you have problems with the screen later, you can use putty to connect to the virtual machine (providing you can see the screen well enough to login, type clear, and then type ifconfig to get the IP address).
  • Once the install has completed, the virtual machine will reboot but Ubuntu won’t start because it can’t find the hard disk. If you don’t get the grub menu, reboot with shift held down.
  • Press ‘e’ to edit the command, and on the line beginning “linux”, add the text noreplace-paravirt vga=791 ata_piix.prefer_ms_hyperv=0 before quiet.
  • Providing the magic vga number worked, you should be able to press F10 and boot into a visible virtual machine.
  • sudo into your favourite editor and edit /etc/default/grub to set the parameter
  • GRUB_CMDLINE_LINUX_DEFAULT = "noreplace-paravirt vga=791 ata_piix.prefer_ms_hyperv=0 quiet"
  • Save changes and exit
  • sudo update-grub
  • sudo reboot

Hopefully you should now have a successful boot.

Friday, 22 February 2013

Android Tablet: Simpsons Tapped Out - Someone Else’s Game Data

This is to help anyone else who has the problem we had with a generic Android tablet, and games that store the progress ‘in the cloud’ – like The Simpsons Tapped Out.

After installing this game on a new, generic Android tablet running Ice Cream Sandwich, the game started at level 15 with someone else’s data.

It turns out that the ‘unique’ identifier “Secure.Android_ID” wasn’t as unique as it should have been, apparently not an unusual problem with small manufacturers’ ROMs.

Note that changing the device ID could screw up your installed apps, lose your existing game data, make you ill or set fire to your pants (in approximate descending order of likelihood). Do this at your own risk. You know you will anyway.

It’s possible to change the Android_ID value (note – not the telephony ID, which is related to phones’ IMEI numbers) providing the tablet is rooted.

I installed and ran Root Checker Basic and found that the tablet was supplied rooted.

So then I installed and ran Device Mod to change the Android_Id value. I used 16 random hex digits from a WEP Key Generator as the new ID.

Then to make sure the new id was picked up by the game, I uninstalled it, rebooted the tablet, deleted the data folder for the game from Android/Data using a File Explorer like this one, and then reinstalled the game.

I hope this helps someone else in the same position. Or me again at some point in the future.