Adding solution items from VisualStudio extension - c#

I am making a test VisualStudio extension that adds new items to solution. I found the way of doing it through trial and error, but I am not satisfied with the complexity of this solution and have some opened questions.. Here is the code:
using EnvDTE;
using EnvDTE80;
//..
namespace MyFirstExtension
{
internal sealed class AddFileToSolution
{
public const int CommandId = PackageIds.CmdCreateVaultHelperConfig;
public static readonly Guid CommandSet = new Guid(PackageGuids.guidVaultHelperVsExtensionPackageCmdSetString);
private readonly AsyncPackage package;
private AddFileToSolution(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}
public static AddFileToSolution Instance { get; private set; }
private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider { get { return this.package; } }
public static async Task InitializeAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
Instance = new AddFileToSolution(package, commandService);
}
private async void Execute(object sender, EventArgs e)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
string fileFullPath = #"C:\Users\myusr\source\repos\VsExtensionTesting2\0.txt";
var dte = await package.GetServiceAsync(typeof(DTE));
var dte2 = dte as DTE2;
var solution2 = (Solution2)dte2.Solution;
// following line throws Exception!
//solution2.AddFromFile(fileFullPath);
bool isSolutionItemsFolderExists = IsSolutionItemsFolderExists(solution2);
if (!isSolutionItemsFolderExists)
{
solution2.AddSolutionFolder("Solution Items");
}
AddToSolutionItems(solution2, fileFullPath);
}
public bool IsSolutionItemsFolderExists(Solution2 solution2)
{
foreach (var solutionItemObj in solution2)
{
var solutionItem = solutionItemObj as Project;
string name = solutionItem.Name;
if (name == "Solution Items")
{
return true;
}
}
return false;
}
public void AddToSolutionItems(Solution2 solution2, string fileFullPath)
{
foreach (var solutionItemObj in solution2)
{
var solutionItem = solutionItemObj as Project;
string name = solutionItem.Name;
if (name == "Solution Items")
{
solutionItem.ProjectItems.AddFromFile(fileFullPath);
}
}
}
}
}
Here I am using the Visual Studio 2019 VSIX project template. It uses AsyncPackage by default. What I am trying to accomplish is to execute command Project.AddExistingItem on solution level.
Questions are:
In my solution - why does calling the command solution2.AddFromFile(fileFullPath); directly throws exception?
System.Runtime.InteropServices.COMException: 'The template specified cannot be found. Please check that the full path is correct.'
The file 100% exists in the directory.. Ideally I want to avoid creating Solution Items folder myself and let the API handle it..
When iterating the solution items/projects I am casting the items to type Project, however, during the debugging I can see that the item is also represented by type System.__ComObject and Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject however it's not possible to cast the item to either of the 2 types.. What are these other 2 types (or the correlation between 3 types) and how do I know exactly which type I am supposed to cast the solution items to?
What is the use-case for finding the commands executed by VS? I have downloaded the command explorer and captured the Project.AddExistingItem command, but how do I actually use this knowledge to execute the command from code? Here is how the command looks like:
I would be glad to hear some explanations about the questions I mentioned above.. My experience with the VS extension building so far - it's lots of googling and trial and error.. (as opposed to knowing actually what to do :) )

Related

Find all references of a symbol that is attributed with some special attribute

I want to craft an analyzer that will throw a message (severity = info) for every occurence of some attributed member in code. That mimicks the behaviour of [Obsolete(...)] but throws only a message instead.
The attribute definition would be something like
public class ThrowsMessageAttribute : Attribute
{
// ...
}
The member I want to throw a message for would then be attributed with it:
public class Foo
{
[ThrowsMessage]
public void Bar() { }
}
For each Bar() I use in my code, I would now get an entry in the message tab of the error list.
My starting point is an empty DiagnosticAnalyzer class:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class MyDiagnosticAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, true, Description, HelpLink);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
{
// how to go on from here?
}
}
Having an AnalysisContext how do I move on? What is the logic that I need to implement in ordner to find all references of symbols which are attributed in a distinguished way?
Maybe I am completely on the wrong track and solving this problem should not be done via an analyzer. What other options are available?
EDIT
Based on the suggestion of #Tamás I got it working almost with the following code:
public override void Initialize(AnalysisContext context)
{
context.RegisterSemanticModelAction(Analyze);
}
private static void Analyze(SemanticModelAnalysisContext context)
{
var semanticModel = context.SemanticModel;
var step2 = GetSymbolsOfAttributedMethods(semanticModel, "ThrowsMessage");
Step3(context, list2, semanticModel);
}
private static List<ISymbol> GetSymbolsOfAttributedMethods(SemanticModel semanticModel, string attributeName)
{
var methodDeclarations = semanticModel.SyntaxTree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
var symbolList = new List<ISymbol>();
foreach (var declaration in methodDeclarations)
{
foreach (var attributeList in declaration.AttributeLists)
{
if (attributeList.Attributes.Any(a => (a.Name as IdentifierNameSyntax)?.Identifier.Text == attributeName))
{
symbolList.Add(semanticModel.GetDeclaredSymbol(declaration));
break;
}
}
}
return symbolList;
}
private static void Step3(SemanticModelAnalysisContext context, List<ISymbol> attributedSymbols, SemanticModel semanticModel)
{
var invocationExpressions = semanticModel.SyntaxTree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocationExpressions)
{
var symbol = semanticModel.GetSymbolInfo(invocation).Symbol;
if (attributedSymbols.Contains(symbol))
{
var l = Location.Create(context.SemanticModel.SyntaxTree, invocation.FullSpan);
context.ReportDiagnostic(Diagnostic.Create(Rule, l));
}
}
}
This works as expected but the location for which I am reporting the diagnostic is not yet quite right, because it is not only the invocation but also the trailing whitespace. Why is that?
Here's the route I would take:
Register a SemanticModelAction with context.RegisterSemanticModelAction
Find the MethodDeclaration of methods with your special attribute and get the method's symbol. This would look something like this:
private List<ISymbol> GetSymbolsOfAttributedMethods(string attributeName)
{
var methodDeclarations = semanticModel.SyntaxTree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
var symbolList = new List<ISymbol>();
foreach (var declaration in methodDeclarations)
{
foreach (var attributeList in declaration.AttributeLists)
{
if (attributeList.Attributes.Any(a => (a.Name as IdentifierNameSyntax)?.Identifier.Text == attributeName))
{
symbolList.Add(semanticModel.GetDeclaredSymbol(declaration));
break;
}
}
}
return symbolList;
}
semanticModel can be aquired from the context of the action you registered.
Go through all InvocationExpressions (getting them in a similar fashion as we did with the methodDeclarations, load their symbol (make sure you use GetSymbolInfo(invocation).Symbol here and not GetDeclaredSymbol as we did earlier).
Compare the symbols from step 3 to the symbols from step 2, and ReportDiagnostic if the invocation's symbol is among the ones with the special attribute.
EDIT
Regarding your edit, it is because you are using FullSpan.
The absolute span of this node in characters, including its leading and trailing trivia.
Either use Span or use invocation.GetLocation() and forget about creating the Location object altogether.
The Roslyn reference is pretty thorough so it's usually a good place to look. And don't forget the Syntax Visualizer, the other tool that can make your life a 100 times easier.

Exception thrown: 'System.MissingMethodException'

I'm working in a WinForm app in 4 layers:
DAL (Data access)
BOL (Bussiness objects)
BAL (Bussiness access)
INT (Intermediate access).
I'm using the Intermediate layer to run any operation needed by the Presentation layer, trying to make it independent, as we can use it in a WinForm, ASP, and so.
I've created a Class that executes those operations like this:
// Clase: ProjectStatusMID
using System.Collections.Generic;
namespace Trevo.FrameWork
{
public class ProjectStatusMID
{
#region Propiedades
private ProjectStatusBOL _Data = new ProjectStatusBOL();
private ProjectStatusBAL _Operations = new ProjectStatusBAL();
private Acciones _Action = Acciones.Nada;
#endregion Propiedades
public ProjectStatusBOL Data
{
get { return _Data; }
set
{
_Data = value;
}
}
public ProjectStatusBAL Operations
{
get { return _Operations; }
set
{
_Operations = value;
}
}
public Acciones Action
{
get { return _Action; }
set
{
_Action = value;
}
}
public int IDProject
{
get { return _Data.IDProject; }
set
{
_Data.IDProject = value;
}
}
public List<Codigos> ProjectsList
{
get { return LoadProjects(); }
}
public ProjectStatusMID()
{
//Load();
}
public void Load()
{
Operations.Consultar(Data);
}
public List<Codigos> LoadProjects()
{
List<Codigos> oRet = new List<Codigos>();
MyProjectsBAL _Operations = new MyProjectsBAL();
MyProjectsBOL _Data = new MyProjectsBOL();
List<MyProjectsBOL> _MyList = _Operations.Lista(_Data);
foreach (MyProjectsBOL o in _MyList)
{
oRet.Add(new Codigos(o.IDProject, o.Project));
}
return oRet;
}
}
}
// Clase: ProjectStatusMID
At the front-end (in this case is WinForm), we are instancing this class as follows:
ProjectStatusMID OO = new ProjectStatusMID();
So, the issue comes when calling one of the methods:
parProject.DataSource = OO.LoadProjects();
Everything is referenced, the app compiles without any problems, the project that contains the class is part of the solution in a separated project (as any other layer), BUT we have the following error:
System.MissingMethodException occurred
HResult=-2146233069
Message=Método no encontrado: 'System.Collections.Generic.List`1 Trevo.FrameWork.ProjectStatusMID.LoadProjects()'.
Source=WorkLoadPresentation
StackTrace:
en Trevo.FrameWork.PS_ProjectStatus_Datos.CargarListas()
en Trevo.FrameWork.PS_ProjectStatus_Datos.PS_ProjectStatus_Datos_Load(Object sender, EventArgs e) en C:\Users\fbravo\OneDrive\Particular_Sistemas\WorkLoad\WorkLoadPresentation\ProjectStatus\PS_ProjectStatus_Datos.cs:línea 25
InnerException:
I've tried to make the class static, re-creating the entire app, deleting the GAC, and so, but a week loose trying different things.
Any help will be appreciated
Could be several issues. The most common one is that you included the DLL library which is the wrong version (e.g. without the method that's missing). Easiest thing to do is to open the exe in the decompiler (e.g. Reflector) and step through it.
Another issue could be the wrong bitness (but probably not).
You have to make sure you referenced the external project dll in your main Winforms application

C# Code Contracts with Builder Pattern - "Possibly calling a method on a null reference"

I'm investigating Code Contracts and I'm implementing the Builder Pattern like this:
public class PersonCaution
{
private PersonCaution()
{
}
public string CautionType { get; private set; }
public string Remarks { get; private set; }
public class Builder
{
private string _cautionType;
private string _remarks;
public Builder WithCautionType(string value)
{
Contract.Ensures(Contract.Result<Builder>() != null);
_cautionType = value;
return this;
}
public Builder WithRemarks(string value)
{
Contract.Ensures(Contract.Result<Builder>() != null);
_remarks = value;
return this;
}
public PersonCaution Build()
{
Contract.Ensures(Contract.Result<PersonCaution>() != null);
return new PersonCaution
{
CautionType = _cautionType,
Remarks = _remarks
};
}
}
}
Here's a snippet showing how I use the Builder class:
if (row != null)
{
var builder = new PersonCaution.Builder()
.WithCautionType((string)row.Element("PersonCaution__Type1G"))
.WithRemarks((string)row.Element("PersonCaution__Remarks"));
if (builder != null)
{
personCautions.Add(builder.Build());
}
}
However, the Code Contracts static checker fails with this error:
Possibly calling a method on a null reference. Do you expect that NWP.PointServices.Domain.Model.People.PersonCaution+Builder.WithCautionType(System.String) returns non-null?
Q. I thought that the Contract.Ensures postcondition would satisfy the static checker, but it doesn't. What do I need to do to remove the error? Thanks v much.
Note. I only see the issue if the Builder class is in a separate Project to the code which calls it.
More info:
Visual Studio Professional 2015 14.0.25424.00 Update 3
All projects target .Net 4.6.1
Code Contracts installed via Visual Studio Extension, v 1.8
I'm successfully using Code Contracts in other (non Builder) areas of the project
As we have found out, all you need is to enable build for contract references from CC project tab to enable cross-project analysis ("Contract Reference Assembly" = "Build")

Dynamically load dlls

I have following Main method. LocalCabelTV and LocalDishTV classes are in the main application program. The program works fine.
I want to keep LocalCabelTV and LocalDishTV in separate dll files. I wonder then how will I load those classes at runtime? I understand then we'll not use switch but for loop to look for all dll files that implements IVideoSource interface in a particular directory and load those...
Need to know how to dynamically load dlls and create objects and use their methods ?
foreach (string dll in Directory.GetFiles("C:\DLLs\*.dll"))
{
Assembly assemb = Assembly.LoadFrom(dll);
??
}
Following works fine:
static void Main(string[] args)
{
SmartTv myTv = new SmartTv();
Console.WriteLine("Select A source to get TV Guide and Play");
Console.WriteLine("1. Local Cable TV\n2. Local Dish TV");
ConsoleKeyInfo input = Console.ReadKey();
switch (input.KeyChar)
{
case '1':
myTv.VideoSource = new LocalCabelTv();
break;
case '2':
myTv.VideoSource = new LocalDishTv();
break;
}
Console.WriteLine();
myTv.ShowTvGuide();
myTv.PlayTV();
Console.ReadKey();
}
class SmartTv
{
IVideoSource currentVideoSource = null;
public IVideoSource VideoSource
{
get
{
return currentVideoSource;
}
set
{
currentVideoSource = value;
}
}
public void ShowTvGuide()
{
if (currentVideoSource != null)
{
Console.WriteLine(currentVideoSource.GetTvGuide());
}
else
{
Console.WriteLine("Please select a Video Source to get TV guide from");
}
}
public void PlayTV()
{
if (currentVideoSource != null)
{
Console.WriteLine(currentVideoSource.PlayVideo());
}
else
{
Console.WriteLine("Please select a Video Source to play");
}
}
class LocalCabelTv : IVideoSource
{
const string SOURCE_NAME = "Local Cabel TV";
string IVideoSource.GetTvGuide()
{
return string.Format("Getting TV guide from - {0}", SOURCE_NAME);
}
string IVideoSource.PlayVideo()
{
return string.Format("Playing - {0}", SOURCE_NAME);
}
}
class LocalDishTv : IVideoSource
{
const string SOURCE_NAME = "Local DISH TV";
string IVideoSource.GetTvGuide()
{
return string.Format("Getting TV guide from - {0}", SOURCE_NAME);
}
string IVideoSource.PlayVideo()
{
return string.Format("Playing - {0}", SOURCE_NAME);
}
}
To load this assembly at runtime and create an object:
Assembly MyDALL = Assembly.Load("DALL"); // DALL is name of my dll
Type MyLoadClass = MyDALL.GetType("DALL.LoadClass"); // LoadClass is my class
object obj = Activator.CreateInstance(Type.GetType("DALL.LoadClass, DALL", true));
For your dynamic Method you can also use Dynamic Method . Its faster than reflection (This method takes only 1/10th time needed by Activator.)
Here is a sample code for creating Object using Dynamic Method.
void CreateMethod(ConstructorInfo target)
{
DynamicMethod dynamic = new DynamicMethod(string.Empty,
typeof(object),
new Type[0],
target.DeclaringType);
methodHandler = (MethodInvoker)dynamic.CreateDelegate(typeof(MethodInvoker));
}
Check out these link for more info: Load Assembly at runtime and create class instance
EDIT: As user #taffer mentioned the DynamicMethod.CreateDelegate is much more slower than reflection. So I would use this only if the created delegate is invoked hundreds or thousands of times. Using Activator with a cache is faster. Secondly, Activator is really fast for parameterless constructors, unless you instantiate so many types, which renders the inner small cache useless.
You need to load the DLLs with the desire classes and iterate over their types, than look for those that implement IVideoSource and activate them:
public static IEnumerable<IVideoSource> GetVideoSources(List<string> assemblyPathes)
{
IEnumerable<Assembly> yourAssembliesWithClasses = assemblyPathes.Select(x => Assembly.Load(x));
IEnumerable<Type> implementingTypes = yourAssembliesWithClasses
.GetTypes()
.Where(x => x.IsAssignableFrom(IVideoSource));
return implementingTypes.Select(x => Activator.CreateInstance(x));
}
Note that Activator.CreateInstance() requires the types to have an empty constructor, if they don't have one you can use, Type.GetUniGetUninitializedObject(Type type) from FormatterServices to initialize them.

Create a SQLite Database in Windows Phone 8.1 Class Library

I have a Windows Phone 8.1 Class Library that I want to later add as a reference to a Windows Phone 8.1 App project.
This ClassLibrary should be responsible for creating and managing its own database. I tried creating a new SQLiteConnection in my ClassLibrary, but it throws the following error: A first chance exception of type 'System.InvalidOperationException' occurred in SQLitePCL.DLL however, if I do the same in my MainApp everything works fine.
So, is it possible to create a SQLite database in a ClassLibrary that's responsible for creating and managing it without any support from the MainApp.
I have a project in it where the SQLite library is in a class library and then I use another class library for the communication between my app and the SQLite library
Class library: SQLite.Library
Make a new class library (in my case I named it SQLite.Library)
Right click > Manage NuGet packages > sqlite-net (https://www.nuget.org/packages/sqlite-net/1.0.8)
After adding this NuGet package you see that your class library has 2 new classes: SQLite.cs and SQLiteAsync.cs.
Also there is a known problem with SQLite and threading (NullReferenceException when page Loads), you can fix it by adding a lock in the method TableMapping GetMapping in SQLite.cs:
public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None)
{
if (_mappings == null) {
_mappings = new Dictionary<string, TableMapping> ();
}
lock (_mappings)
{
TableMapping map;
if (!_mappings.TryGetValue(type.FullName, out map))
{
map = new TableMapping(type, createFlags);
_mappings[type.FullName] = map;
}
return map;
}
}
Class library: Solutionname.Lib
Make a new class library (in my case I named it Solutionname.Lib)
Right click > Add Reference > Solution > SQLite.Library (the class library u just made)
After the reference is set u can use the SQLite library in this class library.
In my project I tried to split my code a bit so I started with making a class named DatabaseHelper.cs:
public class DatabaseHelper
{
private String DB_NAME = "DATABASENAME.db";
public SQLiteAsyncConnection Conn { get; set; }
public DatabaseHelper()
{
Conn = new SQLiteAsyncConnection(DB_NAME);
this.InitDb();
}
public async void InitDb()
{
// Create Db if not exist
bool dbExist = await CheckDbAsync();
if (!dbExist)
{
await CreateDatabaseAsync();
}
}
public async Task<bool> CheckDbAsync()
{
bool dbExist = true;
try
{
StorageFile sf = await ApplicationData.Current.LocalFolder.GetFileAsync(DB_NAME);
}
catch (Exception)
{
dbExist = false;
}
return dbExist;
}
private async Task CreateDatabaseAsync()
{
//add tables here
//example: await Conn.CreateTableAsync<DbComment>();
}
}
After the creation of the DatabaseHelper class u can start by making a datasource class for each table in your database.
In my case i have a CommentDataSource.cs:
public class CommentDataSource
{
private DatabaseHelper db;
public CommentDataSource(DatabaseHelper databaseHelper)
{
this.db = databaseHelper;
}
public async Task<long> AddComment(String vat, String comment)
{
long id = 0;
DateTime date = DateTime.Now;
DbComment dbc = new DbComment(vat, comment, date);
await db.Conn.InsertAsync(dbc);
DbComment insertDbc = await db.Conn.Table<DbComment>().ElementAtAsync(await db.Conn.Table<DbComment>().CountAsync() - 1);
if (insertDbc != null)
{
id = insertDbc.Id;
}
return id;
}
public async void RemoveComment(long idComment)
{
DbComment comment = await db.Conn.Table<DbComment>().Where(c => c.Id == idComment).FirstOrDefaultAsync();
if (comment != null)
{
await db.Conn.DeleteAsync(comment);
}
}
public async Task<List<DbComment>> FetchAllComments(String vat)
{
return await db.Conn.Table<DbComment>().Where(x => x.VAT == vat).ToListAsync();
}
}
As you can see all the datasources that u will add will make use of the same databasehelper.
Use the Solutionname.Lib in your app
Right click > Add Reference > Solution > SQLite.Library (the class library u just made)
Right click > Add Reference > Solution > Solutionname.Lib
You still need to add a reference to your sqlite lib otherwise you will get errors.
Now you can start using your datasource classes, like u can see here:
private DatabaseHelper db = new DatabaseHelper();
private CommentDataSource commentDataSource;
public MainPage()
{
this.InitializeComponent();
commentDataSource = new CommentDataSource(db);
}
Now is every method of the CommentsDataSource available in your app.
Hope this help u a bit!
try this
public async Task<bool> CheckDbAsync(string dbName)
{
bool dbExist = true;
try
{
StorageFile sf = await ApplicationData.Current.LocalFolder.GetFileAsync(dbName);
}
catch (Exception)
{
dbExist = false;
}
return dbExist;
}
public async Task CreateDatabaseAsync(string dbName)
{
SQLiteAsyncConnection con = new SQLiteAsyncConnection(dbName);
await con.CreateTableAsync<ChatClass>();
// await con.CreateTableAsync<RecentChatManageClass>();
await con.CreateTableAsync<PurchasedGift>();
// await con.CreateTableAsync<AttandanceManagement>();
}
and use like this
DataBaseOperation databaseoperation = new DataBaseOperation();
bool existDb = await databaseoperation.CheckDbAsync("sample.db"); // Check Database created or not
if (!existDb)
{
await databaseoperation.CreateDatabaseAsync("sample.db"); // Create Database
}

Categories

Resources