Find dependencies of SDK-style projects - visual-studio-extensions

In a Visual Studio extension I need to iterate through all the projects in the loaded solution and find all Dependencies (both NuGet packages as well as assemblies). The following code works fine for old non-SDK style projects but not for the new SDK-style projects.
var dte = ApplicationObject; // DTE object
if (dte == null || dte.Solution == null || !dte.Solution.IsOpen)
{
return;
}
foreach (Project project in dte.Solution.Projects)
{
var vsProj = project.Object as VSProject;
if (vsProj == null || vsProj.References == null)
{
// Project not loaded
continue;
}
foreach (Reference reference in vsProj.References)
{
if (reference.SourceProject != null
|| reference.Type != prjReferenceType.prjReferenceTypeAssembly)
{
// Skip over non assembly references
continue;
}
// reference.Name contains the reference name
}
}
In the new SDK-style projects "references" is now called Dependencies but there is no such property on VSProject. So what is the way to get this for SDK-style projects?

So what is the way to get this for SDK-style projects?
I'm afraid the answer is negative. This could be one issue related to VS2019 SDK and new SDK project system.
And this issue occurs specifically in IVsSolutionEvents.OnAfterOpenSolutionevent. I tried to invoke similar code in OnQueryCloseSolution event or in a Command Item's click event but it all works well for same solution with one .net core applications in it.
I think it could be one issue cause the solution and project are not null(I output the xx.sln and xx.csproj), but it just can't get reference.Name from vsProj.References. And it works when close solution, but get empty list when open solution.For now, I've found no valid workaround which makes it work.
Issue reported to DC, if anyone's interested in it, you can track the issue here.

The VS2019 project is a .net core 3.1 library project and the following code while the solution is open. is in fact retrieving the path location of a reference stored in the nuget location:
C:\Users\xxxxx\.nuget\packages\subsonic.core.dal\4.2.1\lib\netstandard2.1\SubSonic.Core.DataAccessLayer.dll
public string ResolveAssemblyReference(string assemblyReference)
{
ThreadHelper.ThrowIfNotOnUIThread();
string path = EngineHost.ResolveAssemblyReference(assemblyReference);
if (path.Equals(assemblyReference, StringComparison.Ordinal) &&
!foundAssembly.IsMatch(path))
{ // failed to find the assembly, could it be referenced via a project reference?
if (GetService(typeof(DTE)) is DTE dTE)
{
foreach (Project project in dTE.Solution.Projects)
{
if (project.Object is VSProject vsProject)
{
path = ResolveAssemblyReferenceByProject(assemblyReference, vsProject.References);
}
else if (project.Object is VsWebSite.VSWebSite vsWebSite)
{
path = ResolveAssemblyReferenceByProject(assemblyReference, vsWebSite.References);
}
}
}
if (!foundAssembly.IsMatch(path))
{
LogError(false, SubSonicCoreErrors.FileNotFound, -1, -1, $"{assemblyReference}.dll");
}
}
return path;
}
private string ResolveAssemblyReferenceByProject(string assemblyReference, References references)
{
foreach (Reference reference in references)
{
if (reference.Name.Equals(assemblyReference, StringComparison.OrdinalIgnoreCase))
{ // found the reference
return reference.Path;
}
}
return assemblyReference;
}
private string ResolveAssemblyReferenceByProject(string assemblyReference, VsWebSite.AssemblyReferences references)
{
foreach (VsWebSite.AssemblyReference reference in references)
{
if (reference.Name.Equals(assemblyReference, StringComparison.OrdinalIgnoreCase))
{ // found the reference
return reference.FullPath;
}
}
return assemblyReference;
}

Related

Web Api Help Page XML comments from more than 1 files

I have different plugins in my Web api project with their own XML docs, and have one centralized Help page, but the problem is that Web Api's default Help Page only supports single documentation file
new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Documentation.xml"))
How is it possible to load config from different files? I wan to do sth like this:
new XmlDocumentationProvider("PluginsFolder/*.xml")
You can modify the installed XmlDocumentationProvider at Areas\HelpPage to do something like following:
Merge multiple Xml document files into a single one:
Example code(is missing some error checks and validation):
using System.Xml.Linq;
using System.Xml.XPath;
XDocument finalDoc = null;
foreach (string file in Directory.GetFiles(#"PluginsFolder", "*.xml"))
{
if(finalDoc == null)
{
finalDoc = XDocument.Load(File.OpenRead(file));
}
else
{
XDocument xdocAdditional = XDocument.Load(File.OpenRead(file));
finalDoc.Root.XPathSelectElement("/doc/members")
.Add(xdocAdditional.Root.XPathSelectElement("/doc/members").Elements());
}
}
// Supply the navigator that rest of the XmlDocumentationProvider code looks for
_documentNavigator = finalDoc.CreateNavigator();
Kirans solution works very well. I ended up using his approach but by creating a copy of XmlDocumentationProvider, called MultiXmlDocumentationProvider, with an altered constructor:
public MultiXmlDocumentationProvider(string xmlDocFilesPath)
{
XDocument finalDoc = null;
foreach (string file in Directory.GetFiles(xmlDocFilesPath, "*.xml"))
{
using (var fileStream = File.OpenRead(file))
{
if (finalDoc == null)
{
finalDoc = XDocument.Load(fileStream);
}
else
{
XDocument xdocAdditional = XDocument.Load(fileStream);
finalDoc.Root.XPathSelectElement("/doc/members")
.Add(xdocAdditional.Root.XPathSelectElement("/doc/members").Elements());
}
}
}
// Supply the navigator that rest of the XmlDocumentationProvider code looks for
_documentNavigator = finalDoc.CreateNavigator();
}
I register the new provider from HelpPageConfig.cs:
config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/")));
Creating a new class and leaving the original one unchanged may be more convenient when upgrading etc...
Rather than create a separate class along the lines of XmlMultiDocumentationProvider, I just added a constructor to the existing XmlDocumentationProvider. Instead of taking a folder name, this takes a list of strings so you can still specify exactly which files you want to include (if there are other xml files in the directory that the Documentation XML are in, it might get hairy). Here's my new constructor:
public XmlDocumentationProvider(IEnumerable<string> documentPaths)
{
if (documentPaths.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(documentPaths));
}
XDocument fullDocument = null;
foreach (var documentPath in documentPaths)
{
if (documentPath == null)
{
throw new ArgumentNullException(nameof(documentPath));
}
if (fullDocument == null)
{
using (var stream = File.OpenRead(documentPath))
{
fullDocument = XDocument.Load(stream);
}
}
else
{
using (var stream = File.OpenRead(documentPath))
{
var additionalDocument = XDocument.Load(stream);
fullDocument?.Root?.XPathSelectElement("/doc/members").Add(additionalDocument?.Root?.XPathSelectElement("/doc/members").Elements());
}
}
}
_documentNavigator = fullDocument?.CreateNavigator();
}
The HelpPageConfig.cs looks like this. (Yes, it can be fewer lines, but I don't have a line limit so I like splitting it up.)
var xmlPaths = new[]
{
HttpContext.Current.Server.MapPath("~/bin/Path.To.FirstNamespace.XML"),
HttpContext.Current.Server.MapPath("~/bin/Path.To.OtherNamespace.XML")
};
var documentationProvider = new XmlDocumentationProvider(xmlPaths);
config.SetDocumentationProvider(documentationProvider);
I agree with gurra777 that creating a new class is a safer upgrade path. I started with that solution but it involves a fair amount of copy/pasta, which could easily get out of date after a few package updates.
Instead, I am keeping a collection of XmlDocumentationProvider children. For each of the implementation methods, I'm calling into the children to grab the first non-empty result.
public class MultiXmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
private IList<XmlDocumentationProvider> _documentationProviders;
public MultiXmlDocumentationProvider(string xmlDocFilesPath)
{
_documentationProviders = new List<XmlDocumentationProvider>();
foreach (string file in Directory.GetFiles(xmlDocFilesPath, "*.xml"))
{
_documentationProviders.Add(new XmlDocumentationProvider(file));
}
}
public string GetDocumentation(System.Reflection.MemberInfo member)
{
return _documentationProviders
.Select(x => x.GetDocumentation(member))
.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x));
}
//and so on...
The HelpPageConfig registration is the same as in gurra777's answer,
config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/")));

ASP.NET Entity Framework DataContext use issue

I 've built an ASP.NET website using EF. I created a DataContext class which implements the singleton pattern. My DAO classes (singletons too) instanciate this datacontext and store it in a property. They use it in order to query the SQLServer DataBase. This worked ok for 3 months but I suddenly got exception messages like :"Connection must be valid and open / connection already open". It seemed that datacontext was not disposed. The only change, according to me, was the data size and number of users increasing.
I then found multiple posts saying that singleton was a bad idea foe datacontext, so I tried to instanciate datacontext in a using statement in every request and that resolved the problem, except for update queries which had no effects in database. I had to attach the db object to the context and then set its EntityState to "modified" to have my SaveChanges work.
Like this :
public bool DoucheXpsu(as_headers session) {
using (MyDBEntities MyContext = new MyDBEntities()) {
try {
as_status status = GetStatus(session);
if (status != null) {
if (status.mainstatusvalue == 300) {
status.DateDoucheXpsu = DateTime.Now;
status.DoucheXpsu = 1;
MyContext.as_status.Attach(status);
MyContext.ObjectStateManager.ChangeObjectState(status, EntityState.Modified);
MyContext.SaveChanges();
return true;
} else {
return false;
}
} else {
return false;
}
} catch (OptimisticConcurrencyException) {
return false;
} catch (Exception) {
return false;
}
}
}
The problem is that it actually didn't work for ONE method (which has nothing different from the other update method) !
The exception occured as I tried to attach the object : "The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state. " So I had to comment the attach and ChangeObjectState methods to have it work as expected :
public bool SetSessionToDelete(string numSession) {
using (MyDBEntities MyContext = new MyDBEntities()) {
try {
view_headerStatus view = (from v in MyContext.view_headerStatus
where v.CodeSession == numSession
where v.lastinserted == 1
select v).First();
if (view != null) {
as_status status = (from s in MyContext.as_status
where s.jobclsid == view.jobclsid
where s.lastinserted == 1
select s).First();
if (status != null) {
status.DeleteSession = 1;
//MyContext.as_status.Attach(status);
//MyContext.ObjectStateManager.ChangeObjectState(status, EntityState.Modified);
MyContext.SaveChanges();
return true;
} else {
return false;
}
} else {
return false;
}
} catch (OptimisticConcurrencyException) {
return false;
} catch (Exception) {
return false;
}
}
}
The question is WHY should this one behave differently ???
I've read many posts about EF and dataContext but I feel I'm missing something. I would be glad if anyone can help.
Thanks.
In your first example, this line here:
as_status status = GetStatus(session);
I would assume this populates using a DIFFERENT context, and when it leaves the GetStatus() method the context it used to load is disposed. That is why your subsequent Attach() works. However in your second example you do not need to attach because it was loaded using the current (connected) context.
To solve you may want to either pass the context to your methods like GetStatus() resulting in no need to reattach. I don't typically reattach unless I am resurrecting an object over the wire or from a file.

How can I run custom tool or save a file programmatically using EnvDTE?

I want to save/run custom tools on a handful of .tt files from my extension. I don't want to loop over all the files in the solution/project, rather I want to be able to use a relative (or full) path of the file to execute a save/run custom tool.
Is there a way to get a ProjectItem object given a path of the file ($(SolutionDir)/MyProject/MyFile.tt) so I can execute methods on it?
You can use the FindProjectItem method of the EnvDTE.Solution type to find a file within the current solution by its name. The ExecuteCommand method is dependent on the current UI context; so the item must be selected, otherwise, the call fails.
private bool TryExecuteTextTemplate(string filename)
{
var dte = (DTE2)this.GetService(typeof(SDTE));
Solution solution = dte.Solution;
if ((solution != null) && solution.IsOpen)
{
VSProjectItem projectItem;
ProjectItem item = solution.FindProjectItem(filename);
if (item != null && ((projectItem = item.Object as VSProjectItem) != null))
{
// TODO: track the item in the Solution Explorer
try
{
projectItem.RunCustomTool();
return true;
}
catch (COMException)
{
}
}
}
return false;
}

How to browse the contents of my page ViewState in the VS debugger?

I have an ASP.net web page that is heavily used. The problem is that the ViewState is becoming huge! The page has an ASP.net GridView with paging and sorting. However, the size of the ViewState seems totally out of proportion with what's on the page.
I would like to know how to browse the contents of the ViewState in the Visual Studio 2010 debugger so that I can know what data is being saved in the ViewState.
I'm not sure if it will suit your needs, but you can check out this tool:
http://www.pluralsight-training.net/community/media/p/51688.aspx
And here's helper class you check out which dumps the contents of the ViewState to a log file. Obviously, you can modify it as needed.
// Written by Greg Reddick. http://www.xoc.net
public static void SeeViewState(string strViewState, string strFilename)
{
if (strViewState != null)
{
Debug.Listeners.Clear();
System.IO.File.Delete(strFilename);
Debug.Listeners.Add(new TextWriterTraceListener(strFilename));
string strViewStateDecoded = (new System.Text.UTF8Encoding()).GetString(Convert.FromBase64String(strViewState));
string[] astrDecoded = strViewStateDecoded.Replace("<", "<\n").Replace(">", "\n>").Replace(";", ";\n").Split('\n');
Debug.IndentSize = 4;
foreach (string str in astrDecoded)
{
if (str.Length > 0)
{
if (str.EndsWith("\\<"))
{
Debug.Write(str);
}
else if (str.EndsWith("\\"))
{
Debug.Write(str);
}
else if (str.EndsWith("<"))
{
Debug.WriteLine(str);
Debug.Indent();
}
else if (str.StartsWith(">;") || str.StartsWith(">"))
{
Debug.Unindent();
Debug.WriteLine(str);
}
else if (str.EndsWith("\\;"))
{
Debug.Write(str);
}
else
{
Debug.WriteLine(str);
}
}
}
Debug.Close();
Debug.Listeners.Clear();
//Get into the debugger after executing this line to see how .NET looks at
//the ViewState info. Compare it to the text file produced above.
Triplet trp = (Triplet) ((new LosFormatter()).Deserialize(strViewState));
}
}
And you can call it like this:
DebugViewState.SeeViewState(Request.Form("__VIEWSTATE"), "c:\temp\viewstate.txt")
See this link for more details:
http://www.xoc.net/works/tips/viewstate.asp

How to get folder size in Adobe Air?

How to get folder size in Adobe Air?
Should be fairly simple using File.size. Just in case is confusing, folders in AIR are represented using the File class, which extends FileReference, thus the link to the FileReference documentation.
Recursive folder listings and contents processing
http://cookbooks.adobe.com/post_Recursive_folder_listings_and_contents_processing-9410.html
...has sufficient sample code in it to get you started.
my implementation is:
public static function getFileSize(file:File):Number{
var result:Number = 0;
if(file == null || file.exists == false) {
return 0;
}
if(file.isDirectory){
var files:Array = file.getDirectoryListing();
for each (var f:File in files) {
if(f.isDirectory){
result += getFileSize(f);
}else{
result += f.size;
}
}
}else{
return file.size;
}
return result;
}

Resources