I have an open document in Visual Studio. While my T4 template is running, it writes to the source file of the open document. When the T4 template finishes doing this, I want it to make Visual Studio reload the document in question from its source. The T4 template is triggered automatically upon saving of the document I want to refresh.
How can I do this? I've tried the "View.Refresh" command, but that says "Not Available". I've also tried closing and re-opening the document, but I get the following exception when re-opening it:
Error 3 Running transformation: System.Runtime.InteropServices.COMException (0x8004000C): User canceled out of save dialog (Exception from HRESULT: 0x8004000C (OLE_E_PROMPTSAVECANCELLED))
at EnvDTE.ProjectItem.Open(String ViewKind)
1 1
I've got:
var hostServiceProvider = (IServiceProvider)Host;
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
var projectItem = dte.ActiveDocument.ProjectItem;
var activeDocument = dte.ActiveDocument;
dte.ActiveDocument.Close(EnvDTE.vsSaveChanges.vsSaveChangesNo); // vsSaveChanges value doesn't make a difference - still throws error
projectItem.Open(EnvDTE.Constants.vsViewKindPrimary);
Related
I have a model that I'm mapping to DTO. All the mapping should be set up correctly. I'm using:
Mapper.AssertConfigurationIsValid();
I retrieve the model from the db. Its properties, including its child entities are correct and they are exactly what is found in the tables.
However, when I try to map it to DTO:
var entity = myService.Get(id)
var dto = Mapper.Map<myDTO>(entity);
Visual Studio shows the Open File dialog and tries to open the file PrimitiveExtensions.cs. I can't stop the app, and I need to end Visual Studio from the Task Manager.
This problem occurs for only one particular model (and it's child entities). Does it occur because the mapping is not set up correctly?
Most likely your VS tries to open the source file because it breaks on a first chance exception. Usually you should be able to cancel the file open dialog and then analyze the call stack or simply continue running the code.
You can disable breaking on first chance exception in the Debug -> Exceptions dialog.
In case it isn't only a first chance exception but an unhandled one you can tell VS to not try to open the library code by enabling "Enable just my code" in Tools - Options - Debugging.
I am writing an extension to Visual studio 2012 using VSPackage. I need to add a context menu entry to Test Explorer and on click of this menu item, I need to get the selected unit test(s). I tried to add an item using
((CommandBars)DTE.CommandBars)["Test Window Context Menu"].Controls.Add(Type: MsoControlType.msoControlButton);
and adding an event handler by subscribing to the event
DTE.Events.CommandBarEvents[command].Click
I succeeded in adding an item to Context menu but the Click event handler never gets fired. MSDN said, I needed to set the OnAction property of the command to a valid string value for the Click event handler to get fired. It didn't work either.
Then, I figured out I needed to add a command through the VSCT file in a VSPackage. However, I am not able to find the Test Window Context menu so that I can attach the command to it. Also, I need to get all the unit tests (TestCase objects) listed in the Test Explorer.
Any help is greatly appreciated!
Usually these are the files I look for Visual Studio shell GUIDs or command, context menu, group, etc IDs:
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VSSDK\VisualStudioIntegration\Common\Inc\stdidcmd.h
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VSSDK\VisualStudioIntegration\Common\Inc\vsshlids.h
Actually they are included in the top of your newly created .vsct file (<Extern href="vsshlids.h" />). I guess you've already checked them. I did a quick search, but what I found for "Test" is just a test ribbon and a test dialog. Probably now that you're looking for. It might be still useful for someone finding this post.
You might also want try it brute force style:
Search your Program Files (x86)\Visual Studio [VERSION] for regexp: ^#define.*TEST.*$
This shall give you the defines containing TEST.
Also you might want to consider asking Microsoft directly.
I wrote some exploratory code to loop over commands in that context menu. I also played around with registering a priority command target and seeing what group GUID and command ID I got. The GUID for that context menu appears to be {1e198c22-5980-4e7e-92f3-f73168d1fb63}. You can probably use that to add a command via the .vsct file without using the DTE.CommandBars to add it dynamically.
Here's my experiment code which lists the GUID and command ID of the commands currently in that context menu, in case it helps anyone.
var bars = ((Microsoft.VisualStudio.CommandBars.CommandBars)DTE.CommandBars);
var teContextMenu = bars["Test Window Context Menu"];
var ctls = teContextMenu.Controls;
foreach (var ctl in ctls)
{
var cmdCtl = ctl as Microsoft.VisualStudio.CommandBars.CommandBarControl;
string guid; int id;
DTE.Commands.CommandInfo(ctl, out guid, out id);
Debug.WriteLine($"{cmdCtl?.accName} {guid} {id}");
}
This article on command routing was helpful to me:
https://learn.microsoft.com/en-us/visualstudio/extensibility/internals/command-routing-algorithm
My experimental priority command target, where I set a breakpoint to see what GUID and command IDs were sent, is registered as follows. The TestCommandInterceptor class is a bare-bones implementation of IOleCommandTarget.
var cmdService = GetService(typeof(SVsRegisterPriorityCommandTarget)) as IVsRegisterPriorityCommandTarget;
var target = new TestCommandInterceptor();
cmdService.RegisterPriorityCommandTarget(0, target, out _testCmdInterceptorRegistrationCookie);
I would still like to know the answer to the second part of this question about how to determine the selected tests.
I am developing a Visual Studio package and I have written some code that will make a file in Solution Explorer dependant upon another file.
What this means is that it gives them the same relationship as code-behind files or designer files, where they appear nested under the parent file with a plus/minus icon.
+ MainForm.cs
- MainForm.cs
MainForm.Designer.cs
MainForm.resx
The code that I have written successfully and correctly modifies the underlying project file, however the change is not reflected in Solution Explorer until the project is closed and re-opened.
I'm looking for some code that will refresh or reload the project so that the change is visible in Solution Explorer immediately.
Further Information...
Here is the sudo code that demonstrates the mechanism by which I create the dependant file.
IVsBuildPropertyStorage vsBuildPropertyStorage = GetBuildPropertyStorage();
vsBuildPropertyStorage.SetItemAttribute(projectItemIdentifier, "DependentUpon", parentFileName);
I have also tried adding this in an attempt to get the project to reload, but it doesn't have any effect.
project.Save();
VSProject obj = project.Object as VSProject;
obj.Refresh();
AFAIK the only way of doing this is via automation of the Solution Explorer tool-window:
EnvDTE.DTE dte = ...;
string solutionName = Path.GetFileNameWithoutExtension(dte.Solution.FullName);
string projectName = project.Name;
dte.Windows.Item(EnvDTE.Constants.vsWindowKindSolutionExplorer).Activate();
((DTE2)dte).ToolWindows.SolutionExplorer.GetItem(solutionName + #"\" + projectName).Select(vsUISelectionType.vsUISelectionTypeSelect);
dte.ExecuteCommand("Project.UnloadProject");
dte.ExecuteCommand("Project.ReloadProject");
Note that, if the project hasn't been saved, the user will get a dialog box prior to the "Project.UnloadProject" call.
Here is my code (with reactivating the old window):
public void RefreshSolutionExplorer(EnvDTE.Project activeProject, string captionOfActiveWindow)
{
DTE2 dte2 = activeProject.DTE as DTE2;
string solutionName = Path.GetFileNameWithoutExtension(dte2.Solution.FullName);
string projectName = activeProject.Name;
// Activate SolutionExplorer window
dte2.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate();
// Select your project to be updated
dte2.ToolWindows.SolutionExplorer.GetItem(solutionName + #"\" + projectName).Select(vsUISelectionType.vsUISelectionTypeSelect);
// Refresh SolutionExplorer window
dte2.ExecuteCommand("View.Refresh", String.Empty);
// Reactivate your old window
dte2.Windows.Item(captionOfActiveWindow).Activate();
}
I follow this post Get Visual Studio to run a T4 Template on every build, using the solution with Visual Studio SDK and Visual Studio 2010 Modeling and Visualization SDK installation.
...BUT I get an error that I cannot solve:
Error 2 Running transformation: System.ArgumentNullException: Value cannot be null.
Parameter name: Could not obtain DTE from host
at Microsoft.VisualStudio.TextTemplatingE035559D977B9B9858AB2036321BC47D.GeneratedTextTransformation.EntityFrameworkTemplateFileManager.VsEntityFrameworkTemplateFileManager..ctor(Object textTemplating)
at Microsoft.VisualStudio.TextTemplatingE035559D977B9B9858AB2036321BC47D.GeneratedTextTransformation.EntityFrameworkTemplateFileManager.Create(Object textTransformation)
at Microsoft.VisualStudio.TextTemplatingE035559D977B9B9858AB2036321BC47D.GeneratedTextTransformation.TransformText()
at Microsoft.VisualStudio.TextTemplating.TransformationRunner.RunTransformation(TemplateProcessingSession session, String source, ITextTemplatingEngineHost host, String& result). Line=0, Column=0 ApmWeb.Web.Client
Following the first part of my script...
<## template language="C#" debug="false" hostspecific="true"#>
<## include file="EF.Utility.CS.ttinclude"#>
<## output extension=".cs"#><#
CodeGenerationTools code = new CodeGenerationTools(this);
MetadataLoader loader = new MetadataLoader(this);
CodeRegion region = new CodeRegion(this, 1);
MetadataTools ef = new MetadataTools(this);
string inputFile = #"../../ApmWeb.Infrastructure.Data/Model/ApmWebModel.edmx";
MetadataWorkspace metadataWorkspace = null;
bool allMetadataLoaded =loader.TryLoadAllMetadata(inputFile, out metadataWorkspace);
EdmItemCollection ItemCollection =
(EdmItemCollection)metadataWorkspace.GetItemCollection(DataSpace.CSpace);
string namespaceName = code.VsNamespaceSuggestion();
EntityFrameworkTemplateFileManager fileManager =
EntityFrameworkTemplateFileManager.Create(this);
UPDATE
I found that the problem is into "EF.Utility.CS.ttinclude" in this point...
dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
if (dte == null)
{
throw new ArgumentNullException("Could not obtain DTE from host");
}
My idea is that you can’t get the DTE object when running the transformation outside the VS Host. This error occurs, for example when running the transformation within a Team Build (the MSBuild Host does not “know” the DTE object). In effect it works using "run custom tool" from VS but configuring the autamatic T4 build as I told in previous post, it doesn't work.
So how can solve? Is it a bug of EF.Utility.CS.ttinclude?
UPDATE
Removing interaction with VS using DTE (define PREPROCESSED_TEMPLATE in EF.Utility.CS.ttinclude) all works, but I loose e.g. capability to add generated file to my project... Is there an other way to put working it?
As you've discovered, you're not going to have access to the DTE when transforming at build time.
One approach to dealing with the generated code not appearing in your project is to include it with a wildcard e.g.
As long as you use a standard output naming convention this should give you a reasonable experience. You can also use the build targets to redirect the generated output to a specific folder and then use a simpler wildcard to include everything in the folder.
You do have to watch out that manual tweaking in the IDE will remove this wildcard and replace it with a point in time outcome of its evaluation.
In Visual Studio 2010 I have the following project layout:
Solution
project A
class C
class D
project B
T4 template
The T4 template contains a assembly reference like this:
<## assembly name="$(SolutionDir)\A\bin\Debug\A.dll" #>
The template instantiates an instance of class C. When I run the T4 template the processor loads the project A's dll and correctly creates the output. The error arises when I want to change something in project A, say modify either class C or D.
Unable to copy file "obj\Debug\A.dll"
to "bin\Debug\A.dll". The process
cannot access the file
'bin\Debug\A.dll' because it is being
used by another process.
The only way I found to get rid of this error is to restart Visual Studio. Is there any other way to force the unloading of the A.dll assembly from VS?
Im using VS2010 SP1 and was still getting blocked during build after first build when running a custom T4 template during the POST-BUILD events which accessed instances of classes of the same project.
How I got it to work was to use Reflection to access classes from the Project dll.
I still got the blocking issue when loading the dll directly from the file.
NOTE: The trick was to load the .dll into memory as a byte array and then load the assembly from the raw byte array. DONT load from the file using the Assembly.LoadFrom
This code is from my T4 template file and is accessing a static class "Information" and calling a static Method "Version" to return a string value.
string assemblyPath = Path.Combine(projectPath, #"bin\SampleProject.dll");
byte[] data;
using (var fs = File.OpenRead(assemblyPath))
{
data = new byte[fs.Length];
fs.Read(data, 0, Convert.ToInt32(fs.Length));
}
if (data == null || data.Length == 0)
{
throw new ApplicationException("Failed to load " + assemblyPath);
}
var asm = Assembly.Load(data);
appVersion = (string) asm.GetType("SampleProject.Information").GetField("Version").GetValue(null);
m0sa
This issue has been fixed in Visual Studio 2010 SP1.
If you're not able to use that, there is a VolatileAssembly directive add-on in the T4 Toolbox project on CodeBox (http://t4toolbox.codeplex.com/)