Moving and renaming resource keys in a .resx file

I work on websites where we have several resource files that are localized in many languages. This makes operations like renaming a resource key and moving a resource key to a different file annoying since you must do it across several languages. To help with this I created a couple of PowerShell scripts to automate this process.

These PowerShell scripts assume your .resx files live in a folder named “Resources” but this can be easily changed.

RenameResource.ps1

param([String]$keyOld, [String]$keyNew)
if(-not $keyOld -or -not $keyOld) {
  echo "RenameResource.ps1 keyOld keyNew"
  return
}

$files=get-childitem Resources *.resx

foreach($file in $files) {
  $xml = [xml](Get-Content $file.FullName)
  $dataNode = $xml.root.data | Where-Object { $_.GetAttribute("name") -eq $keyOld }
  if ($dataNode -ne $null) {
    $dataNode.SetAttribute("name", $keyNew)
    $xml.Save($file.FullName)
  }
}

 

MoveResource.ps1

param([String]$resourceNameSource, [String]$resourceNameDestination, [string] $keyName)
if(-not $resourceNameSource -or -not $resourceNameDestination -or -not $keyName) {
  echo "MoveResource.ps1 resourceNameSource resourceNameDestination keyName"
  return
}

$sourceFiles = Get-ChildItem "Resources\$resourceNameSource*.resx"
$destFiles = Get-ChildItem "Resources\$resourceNameDestination*.resx"
$mapping = @{}

$sourceFiles | foreach {
  $parts = $_ -split "\."
  $list = New-Object "system.collections.arraylist"
  $list.Add($_) > $null
  if($parts.Length -eq 2) {
    $mapping["en-us"] = $list
  }
  else {
    $mapping[$parts[1]] = $list
  }
}

$destFiles | foreach {
  $parts = $_ -split "\."
  if($parts.Length -eq 2) {
    $mapping["en-us"].Add($_ ) > $null
  }
  else {
    $mapping[$parts[1]].Add($_ ) > $null
  }
}

foreach($pair in $mapping.Values) {
  $sourcePath = $pair[0]
  $destPath = $pair[1]
  $source = [xml](Get-Content $sourcePath)
  $dest = [xml](Get-Content $destPath)

  $keyNodeSource = $source.root.data | Where-Object { $_.GetAttribute("name") -eq $keyName }
  $keyNodeDest = $dest.root.data | Where-Object { $_.GetAttribute("name") -eq $keyName }
  if ($keyNodeSource -eq $null) {
      echo "Key does not exist in source"
      return;
  }
  if($keyNodeDest -ne $null) {
    echo "Key already exists in destination"
    return;
  }
  try
  {
    $newNode = $dest.ImportNode($keyNodeSource.Clone(), $true)
    $source.root.RemoveChild($keyNodeSource) > $null
    $dest.root.AppendChild($newNode) > $null
    $dest.Save($destPath)
    $source.Save($sourcePath)
  }
  catch {
    Write-Host "Error text: " $_
    return
  }
}

 

Usage

Renaming a resource key:

.\RenameResource.ps1 oldKey newKey

 

Moving a resource with key “keyName”  from a file named “ResourceFile1.resx” to “ResourceFile2.resx”:

.\MoveResource.ps1 ResourceFile1 ResourceFile2 keyName

 

Posted in Localization, PowerShell | Leave a comment

Chutzpah 1.2.0 Released

A new version of Chutzpah is now live on Visual Studio Gallery, CodePlex and now NuGet.

This release contains the following changes.

1. Added the Chutzpah console runner as a NuGet package. Once installed the Chutzpah.Console.exe file will be located in the Tools directory of the Chutzpah package.

2. Added ability from command line and Visual Studio to have Chutzpah scan for test files when you execute it on a folder. This helps alleviate a pain point that arises when you break your JavaScript tests into multiple files. Before this change you would have to specify each file individually in the call to the console runner. Now it can take a folder name instead of a file name.

chutzpah.console.exe someFolder/jsTests

When Chutzpah sees that you provided a folder it will scan recursively within that folder for .js files and run any it believes are QUnit test files.

3. Fixed a bug that caused Chutzpah to error on read-only files. This can be quite common since many source control systems can put files in this state.

4. Added a command line flag /OpenInBrowser which makes Chutzpah open the test files in your default browser.  This can be helpful since it allows you to debug the tests more easily.

Posted in Chutzpah, Codeplex, NuGet, Open Source, Visual Studio Gallery | 5 Comments

Make your website faster with RequestReduce

My co-worker Matt Wrock released an open source project on GitHub and Nuget called RequestReduce.  It is a very quick and easy way to dramatically improve the performance of an ASP.NET website. We use this library for our sites at work and after adding it we saw around an 18% improvement in global page load time.

RequestReduce works by examining the resources in an HTTP request and optimizes them before sending to the user. It will optimize them in the following ways (Matt Wrock’s own words):

  • Look for background images that it can sprite. This is the process of combining multiple images into a single image and using some CSS syntax to pull in specific images from that single file into a CSS class’s background.
  • Merge these images into a single PNG that is quantized down to 256 colors and then run through optipng for lossless compression. Unsatisfied with the quality I was getting from the popular opensource quantizers, I created a quantizer based on the Wu quantization algorithm and have released that separately as nQuant on codeplex. This often reduces the image size up to 3x smaller than the original.
  • Merges all CSS in the head and minifies it.
  • Manages the downloads of these CSS and image requests using ETag and expires headers to ensure optimal caching on the browser.

One of the nicest aspects of RequestReduce is how simple it is to start using. Just download the binaries to your website bin folder and add the following to your web.config:

<system.webServer>
 <modules runAllManagedModulesForAllRequests="true">
  <add name="RequestReduce" type="RequestReduce.Module.RequestReduceModule, RequestReduce, Version=1.0.0.0,
  Culture=neutral" />
 </modules>
</system.webServer>

If you are interested I would definitely head over to the RequestReduce website to read the detailed documentation (including advanced configuration options) and the source code (yay for open source Smile ).

Posted in .NET, ASP.NET, Open Source | Leave a comment

Update on inheriting base type mappings with AutoMapper

Several months ago I wrote this post which shows an extension method for inheriting the mappings on a base type for a child type in AutoMapper.  Since then I have had a few comments on the post which led me to make a small update. One of the assumptions I made in my implementation was that when inheriting base type mappings both of your mapping types will have their base types in the parent mapping. For example:

Mapper.CreateMap<SourceBaseClass, DestBaseClass>()
    .ForMember(x => x.BasePropTwo, m => m.MapFrom(x => x.BaseProp2));
 
Mapper.CreateMap<SourceChildClass, DestChildClass>()
    .ForMember(x => x.ChildPropTwo, m => m.MapFrom(x => x.ChildProp2))
    .InheritMappingFromBaseType();

However, there are scenarios where you may have one type that is mapping to both a parent and child type and would still want to inherit the parent mapping for the child mapping. If you try this with my previous code it would look like this but not work.

Mapper.CreateMap<SourceClass, DestBaseClass>()
    .ForMember(x => x.BasePropTwo, m => m.MapFrom(x => x.BaseProp2));
 
Mapper.CreateMap<SourceClass, DestChildClass>()
    .ForMember(x => x.ChildPropTwo, m => m.MapFrom(x => x.ChildProp2))
    .InheritMappingFromBaseType();

In order to support this scenario I updated the extension method to take an optional enum which specifies if you want to inherit a mapping that has a base type for the source type, destination type or both.

public enum WithBaseFor
{
    Source,
    Destination,
    Both
}

public static class Inheritance
{
    public static void InheritMappingFromBaseType<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression,
                                                                         WithBaseFor baseFor = WithBaseFor.Both)
    {
        var sourceType = typeof (TSource);
        var destinationType = typeof (TDestination);
        var sourceParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Source
                                   ? sourceType.BaseType
                                   : sourceType;

        var destinationParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Destination
                                        ? destinationType.BaseType
                                        : destinationType;

        mappingExpression
            .BeforeMap((x, y) => Mapper.Map(x, y, sourceParentType, destinationParentType))
            .ForAllMembers(x => x.Condition(r => NotAlreadyMapped(sourceParentType, destinationParentType, r)));
    }

    private static bool NotAlreadyMapped(Type sourceType, Type desitnationType, ResolutionContext r)
    {
        return !r.IsSourceValueNull &&
               Mapper.FindTypeMapFor(sourceType, desitnationType).GetPropertyMaps().Where(
                   m => m.DestinationProperty.Name.Equals(r.MemberName)).Select(y => !y.IsMapped()).All(b => b);
    }
}

Using this new code the above mapping becomes

Mapper.CreateMap<SourceClass, DestBaseClass>()
    .ForMember(x => x.BasePropTwo, m => m.MapFrom(x => x.BaseProp2));

Mapper.CreateMap<SourceClass, DestChildClass>()
    .ForMember(x => x.ChildPropTwo, m => m.MapFrom(x => x.ChildProp2))
    .InheritMappingFromBaseType(WithBaseFor.Destination);

There is one caveat to using my InheritMappingFromBaseType extension method that was discovered since my original post … it is not compatible with the Mapper.AssertConfigurationIsValid() call. It will cause this function to fail even though your mapping will work fine. I haven’t had a chance to figure out if there is a way around this but if anyone has an idea I would love to hear it.

I published the updated code and a sample project showing its use to the InheritedAutoMapper GitHub page. Feel free to grab the code and fork it to your liking.

Posted in AutoMapper, github | 1 Comment

Chutzpah 1.1.0 Released

A new version of Chutzpah is now live on Visual Studio Gallery and CodePlex.

This release contains the following changes:

  • Added a “Run JS Tests in Browser” menu option inside of Visual Studio. This will open your default browser and run the selected QUnit test file.
  • Automatically save the test file in Visual Studio when you choose “Run JS Tests”
  • Added keyboard shortcut support for Visual Studio
    • Bind a shortcut to the ProjectAndSolutionsContextMenus.Project.RunJSTests command to run JS tests
    • Return line and column position for failed tests when you run a test JS file directly.
    • Upgraded to PhantomJS 1.2
    Posted in Chutzpah, Open Source | Leave a comment