📜 ⬆️ ⬇️

Quick add links or “goodbye Add Reference”

Recently, I finished one problem that has been getting to me for a very long time. Its essence is that the Add Reference dialog in Visual Studio is not needed if you take the assembly from one of those places where the studio is looking for them. It is not needed because the studio could easily index all the namespaces in these assemblies and, when writing using Biztalk give me the opportunity to add a link automatically. Since the studio cannot do this, I had to help her.


The idea itself is simple, and consists of 2 parts, namely:


Indexing


The base for namespaces and paths to build files is done in seconds. The only trick is to use Cecil instead of perversions such as Assembly.ReflectionOnlyLoad() , which are trying to load the dependencies, and that's all. We quickly find all the types, write them down the names of the names in the HashSet , and put all this in the base. How? We will talk about this now.
')
First, the paths to the files that the Add Reference uses are in at least 2 places - in the registry, and in the PublicAssemblies folder. To find those folders that are listed in the registry, I wrote the following code:



public static IEnumerable< string > GetAssemblyFolders()<br/>
{<br/>
string [] valueNames = new [] { string .Empty, "All Assemblies In" };<br/>
string [] keyNames = new []<br/>
{<br/>
@"SOFTWARE\Microsoft\.NETFramework\AssemblyFolders" ,<br/>
@"SOFTWARE\Wow6432Node\Microsoft\.NETFramework\AssemblyFolders" <br/>
};<br/>
var result = new HashSet< string >();<br/>
foreach ( var keyName in keyNames)<br/>
{<br/>
using ( var key = Registry.LocalMachine.OpenSubKey(keyName))<br/>
{<br/>
if (key != null )<br/>
foreach ( string subkeyName in key.GetSubKeyNames())<br/>
{<br/>
using ( var subkey = key.OpenSubKey(subkeyName))<br/>
{<br/>
if (subkey != null )<br/>
{<br/>
foreach ( string valueName in valueNames)<br/>
{<br/>
string value = (subkey.GetValue(valueName) as string ?? string .Empty).Trim();<br/>
if (! string .IsNullOrEmpty( value ))<br/>
result.Add( value );<br/>
}<br/>
}<br/>
}<br/>
}<br/>
}<br/>
}<br/>
return result;<br/>
}<br/>

Initially, I had little work, because The keys on the 32-bit and 64-bit systems are different. Once again I notice that with the transition to a 64-bit system I started writing better code :)

To find the PublicAssemblies folder, you must first find where Visual Studio is installed:

public static string GetVS9InstallDirectory()<br/>
{<br/>
var keyNames = new string []<br/>
{<br/>
@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\Setup\VS" ,<br/>
@"SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VS" <br/>
};<br/>
foreach ( var keyName in keyNames)<br/>
{<br/>
using ( var key = Registry.LocalMachine.OpenSubKey(keyName))<br/>
{<br/>
if (key != null )<br/>
return key.GetValue( "ProductDir" ).ToString();<br/>
}<br/>
}<br/>
return null ;<br/>
}<br/>

Having a list of folders, you can search each of all DLL-files and index them. In addition to those folders that always appear in the Add Reference dialog, you can add your own folders, which is convenient.

using ( var dc = new StatsDataContext())<br/>
{<br/>
var dirs = new HashSet< string >();<br/>
dirs.Add( @"C:\Program Files (x86)\JetBrains\ReSharper\v4.5\Bin" );<br/>
foreach ( var dir in GetAssemblyFolders()) dirs.Add(dir);<br/>
dirs.Add(Path.Combine(GetVS9InstallDirectory(), @"Common7\IDE\PublicAssemblies" ));<br/>
foreach ( string dir in dirs.Where(Directory.Exists))<br/>
{<br/>
string [] files = Directory.GetFiles(dir, "*.dll" );<br/>
var entries = new HashSet<Namespace>();<br/>
foreach ( string file in files)<br/>
{<br/>
var ns = AddNamespacesFromFile(file);<br/>
foreach ( var n in ns)<br/>
entries.Add(n);<br/>
}<br/>
dc.Namespaces.InsertAllOnSubmit(entries);<br/>
}<br/>
dc.SubmitChanges();<br/>
}<br/>

Adding is done using the AddNamespacesFromFile() method which, as I already wrote, uses Mono.Cecil.

private static IEnumerable<Namespace> AddNamespacesFromFile( string file)<br/>
{<br/>
HashSet<Namespace> result = new HashSet<Namespace>();<br/>
try <br/>
{<br/>
var ad = AssemblyFactory.GetAssembly(file);<br/>
foreach (ModuleDefinition m in ad.Modules)<br/>
{<br/>
foreach (TypeDefinition type in m.Types)<br/>
{<br/>
if (type.IsPublic && ! string .IsNullOrEmpty(type.Namespace))<br/>
{<br/>
result.Add( new Namespace<br/>
{<br/>
AssemblyName = ad.Name.Name,<br/>
AssemblyVersion = ad.Name.Version.ToString(),<br/>
NamespaceName = type.Namespace,<br/>
PhysicalPath = file<br/>
});<br/>
}<br/>
}<br/>
}<br/>
}<br/>
catch <br/>
{<br/>
// it's okay, probably a non-.Net DLL
}<br/>
return result;<br/>
}<br/>

With filling the base on it all. Then you can use the results, although I also made a background utility that allows you to refresh the data and add new paths.

Using


Not having the best options, I implemented adding links as context action for ReSharper. The idea is simple - the user hovers over the word Biztalk in the line using Biztalk; and sees the magic menu, when selecting elements of which a link is automatically added to the project.

The CA itself inherits from the CSharpContextActionBase utility class, within which, besides checking and “applicability”, nothing clever happens. Searching the database is done using a simple SELECT * from Namespaces where NamespaceName LIKE '%BizTalk%' -style selection SELECT * from Namespaces where NamespaceName LIKE '%BizTalk%' . For the base in which you will have a couple of thousand elements (well, maybe 10 thousand if you try), this approach is adequate.

protected override bool IsAvailableInternal()<br/>
{<br/>
items = EmptyArray<IBulbItem>.Instance;<br/>
var element = GetSelectedElement<IElement>( false );<br/>
if (element == null )<br/>
return false ;<br/>
var parent = element.ToTreeNode().Parent;<br/>
if (parent == null || parent.GetType().Name != "ReferenceName" || parent.Parent == null <br/>
|| string .IsNullOrEmpty(parent.Parent.GetText()))<br/>
return false ;<br/>
string s = parent.Parent.GetText();<br/>
if ( string .IsNullOrEmpty(s))<br/>
return false ;<br/>
var bulbItems = new HashSet<RefBulbItem>();<br/>
using ( var conn = new SqlConnection(<br/>
"Data Source=(local);Initial Catalog=Stats;Integrated Security=True" ))<br/>
{<br/>
conn.Open();<br/>
var cmd = new SqlCommand(<br/>
"select * from Namespaces where NamespaceName like '%" + s + "%'" , conn);<br/>
using ( var r = cmd.ExecuteReader())<br/>
{<br/>
int count = 0;<br/>
while (r.Read())<br/>
{<br/>
bulbItems.Add( new RefBulbItem(<br/>
provider,<br/>
r.GetString(2).Trim(),<br/>
r.GetString(3).Trim(),<br/>
r.GetString(4).Trim()));<br/>
count++;<br/>
}<br/>
if (count > 0)<br/>
{<br/>
items = System.Linq.Enumerable.ToArray(<br/>
System.Linq.Enumerable.ThenBy(<br/>
System.Linq.Enumerable.OrderBy(<br/>
bulbItems,<br/>
i => i.AssemblyName),<br/>
i => i.AssemblyVersion));<br/>
return true ;<br/>
}<br/>
}<br/>
}<br/>
return false ;<br/>
}<br/>

Everything interesting happens in BulbItem ah, that is, yellow light bulbs that appear in the process of invoking the context menu. The light bulb itself is a kind of POCO that can add a link to a specific assembly at the right time.

protected override void ExecuteBeforeTransaction(ISolution solution,<br/>
JetBrains.TextControl.ITextControl textControl, IProgressIndicator progress)<br/>
{<br/>
var project = provider.Project;<br/>
if (project == null ) return ;<br/>
var fileSystemPath = FileSystemPath.TryParse(path);<br/>
if (fileSystemPath == null ) return ;<br/>
var assemblyFile = provider.Solution.AddAssembly(fileSystemPath);<br/>
if (assemblyFile == null ) return ;<br/>
var cookie = project.GetSolution().EnsureProjectWritable(project, out project, SimpleTaskExecutor.Instance);<br/>
QuickFixUtil.ExecuteUnderModification(textControl,<br/>
() => project.AddModuleReference(assemblyFile.Assembly),<br/>
cookie);<br/>
}<br/>

I managed to write the code above only with the help of a member of the JetBrains team ( planerist - thanks!), Because I myself did not have the assiduity to find the right way.

Conclusion


I do not know how much I saved time by implementing this functionality, but the headache in the style of "sitting and waiting for Add Reference" definitely became less. And composing projects with my favorite build set (DI, Mocks, validation, utilities, etc.) has become much easier. ■

Source: https://habr.com/ru/post/75831/


All Articles