During the conception and development of new applications, designers tend to do lots of styling trials. After some years the application will be trashed with unused resources. This effect doesn’t only pollute and difficult the development process but also increases the size of your delivery.
The straw that broke the camel’s back
One thing that I hate about Silverlight is the over fluffed default theme. It bounces, explodes and fades like a demo application but for commercial use it is just too much (mostly if your users have crappy machines).
One day I decided to just turn it off. I’m not a Silverlight expert but I didn’t want any of the developers “wasting” time on such a side task. I knew that the previous designer and front-end developers left a mess in the code, but I couldn’t imagine how bad it was. For each type of component there was up to 4 different styles plus the default one randomly used through out the code. Most of them were just artifacts of some old prototype and no longer used. It was time for a clean up.
Browsing on the Web I didn’t find any reasonable solution for finding unused resources in Silverlight projects. There is one application called Pistachio, but it is too fancy and unusable for big projects. So I decided to write my own simple application.
Additional reasons
Cleaning up the styles is not only a way to make your application concise and easier for developers to design new pages. Each single xaml file will be added as a text resource directly to your binary file (they can be read inside of your dlls via any text editor). Even if the binaries are later on zipped in a xap file, the sum of these many-lines-long style files can be significant even after compressing them and sending unused meaningless text resources to all your users doesn’t seem like a best practice.
Quick and Dirty but effective
As mentioned above, I didn’t want to spend much time on this task. So instead of going into the depths of parsing a xaml file, I went to the pragmatic approach of doing string search. Styles like are uniquely identified by a key. If this key is not referenced in any other resource then it is not used. Of course this is not bullet proof but it served my purposes.
The first problem was to read the solution file, as styles may be shared among projects. On .Net 4 you can do some hacking for using an internal class for reading solution files, but on 3.5 it isn’t possible. As a .sln file is a pure structured text file, this task is not so difficult.
public Solution(string filename) { var lines = File.ReadAllLines(filename); foreach (string line in lines.Where(l => l.StartsWith("Project") && l.Contains(".csproj"))) Projects.Add(ParseProjectLine(line)); } private string ParseProjectLine(string line) { var parts = line.Split(','); if (parts.Length < 3) throw new Exception("Could not parse project line " + line); var path = parts[1].Trim(); // Remove quotes if (path.StartsWith("\"")) path = path.Substring(1, path.Length - 2); return path; }
Now the second challenge is to parse the Project files. Project files are xml files and one can use the Microsoft.Build.BuildEngine.Project class for reading them. I didn’t find the usage of this class very straightforward, but after some comings and goings it did the job. For identifying if a project in the solution is a Silverlight one, we cannot rely on the SilverlightApplication property as it is not set for Silverlight Class Libraries. The solution was to look for the Microsoft.Silverlight.CSharp.targets namespace in the Import section (I’m sure if this the best approach).
project.Load(projectFilename); var property = project.EvaluatedProperties["SilverlightApplication"]; bool isSl = property != null && property.Value == "true"; if (!isSl) { isSl = project.Imports.Cast<Import>().Any(n => n.ProjectPath.Contains("Microsoft.Silverlight.CSharp.targets")); if (!isSl) return; }
Once you have the list of the projects and xaml files, you can start playing with the string searches. The first task is to enumerate all the Styles used. Although I hate Regex, this was the fastest solution:
var lines = File.ReadAllText(xaml); // Look for style definition based on Regex match var matches = Regex.Matches(lines, "<(Style|[\\w]*Brush).*x:Key=\"(?<styleName>[\\w]*?)\".*>"); if (matches.Count > 0) { foreach (Match match in matches) { Group grp = match.Groups["styleName"]; if (grp != null && !string.IsNullOrEmpty(grp.Value)) { // Add style to our collection _styles.Add(new KeyValuePair<string, string>(grp.Value, xaml)); } } }
The same Regex strategy is used for the identifying if a style is used or not. In this case we are not looking for the style declaration (<Style or <*Brush) but for the usage.
The Result
Without any additional clean-up, I could delete almost half of the styles:
262 styles found Non-referenced styles 114: (...)
After deleting them I looked for styles that were used only once or twice or that survived the first application restyling. Once this was done I could finally remove the exaggerated Child Window open animation 🙂
The full source and binaries can be found here: CheckSilverlightStyles