A personal hobby of mine is collecting CDs and whenever I acquire a new CD, there is an entire workflow that
happens: The CD gets ripped, the cover art is acquired or created, then the files are processed to embed the
cover art, lyrics, and ReplayGain values. These last three bits are something for which there is no decent
tooling. So, I wrote a tool myself.
The most volatile part is finding and downloading lyrics for the songs. There is a gray area with regard to
lyrics as they are usually included in the booklet “by permission” and are prone to litigation when published. As such, the availability of lyrics and the websites that host them are unpredictable.
Legality concerns aside, the utility I wrote has to cope with these disparate websites that come and go and also the
differences between them. The original version of the lyric utility had a different class for each website,
with specific logic as to how to download and parse the lyrics when given an artist and song title. Pretty
general architecture. I had radio buttons in the application where you could select which site to attempt to
download the lyrics from.
Over time, things got a little annoying. A site would vanish and I’d have to swap it out for another. Write a new class and edit the form to change the radio buttons, then change the code to instantiate the right class
to process the lyrics. I should point out that early on, I implemented an interface so I only had to manage
the instantiation of the right class. More typical architecture, growing as the the application’s requirements
grow.
Finally, I was down to one working website and was ready to add a bunch more, but I was irritated at the thought of
having to modify my UI code to support these new classes. I decided to make the classes external to the
application using a plug-in type design. The interfaces and generic classes I was already using, I made them
public, then created additional projects that referenced the main application and those classes. In the UI, I
changed the radio buttons to a dropdown and loaded it up with instances of classes loaded from DLLs in the bin
folder (I’m speaking very generally, here). Then I moved all my website-specific code into these other class
projects and had the post-build action copy the DLL to the main application’s bin folder. The conversion only
took an hour or so. The rest of the time was creating new parsers for new websites.
The meat of the code is simply looping through the DLLs (which are named consistently) and looping through the
classes in each DLL looking for the classes that implement the interface.
lyricProviders = New Dictionary(Of String, ILyricProvider)
For Each path As String In IO.Directory.GetFiles(My.Application.Info.DirectoryPath, "*lyricprovider.dll")
a = System.Reflection.Assembly.LoadFile(path)
For Each t As Type In a.GetTypes
If t.GetInterfaces.Contains(GetType(ILyricProvider)) Then
p = CType(a.CreateInstance(t.FullName), ILyricProvider)
lyricProviders.Add(p.ProviderName, p)
End If
Next
Next
So this Dictionary has a list of websites and an instance of the class that downloads the information. I look
up a dictionary entry based on the name that was chosen the drop down list. I get an instance to execute to
get the lyrics. Plug-ins utilizing interfaces are not as difficult as they may appear.
Maybe you’re thinking I’ve created more work for myself because if I want to add support for a new website, I have to
create a whole new project to add it to the application. But now, I don’t have to touch any of the code in the
main application ever again. I code to the interfaces and that’s it. If a website dies and I need to no
longer support it, I delete its respective DLL from the bin folder and I’m done. No code changes at all.