How to specify the path to a VS extension DLL from an MSBuild <UsingTask> tag - msbuild

I'm developing a Visual Studio extension. The extension's associated VS project template includes a call to a custom task in the extension's DLL:
<UsingTask TaskName="MyTask" AssemblyFile="path to MyDLL.dll" />
The extension will be installed in the usual place, through use of the VSIX installer.
My question is: Is there a good MSBuild property or macro that I can use to construct the path to the extension's DLL (i.e., MyDLL.dll)? I'm aware of $(DevEnvDir) and could extend that path when using the project and extension in Visual Studio 2015 (append \VendorName\ProductName\Version), but that doesn't seem to work in VS 2017, where the appended path uses a mangled name that can't be predicted ahead of time (or can it?). There's also the issue that the project/extension should work in the VS experimental instance, which does not appear to reflect $(DevEnvDir).
Is there any good way to do this with MSBuild properties, or will I need to look at alternatives like environment variables or the registry?

Is there any good way to do this with MSBuild properties, or will I need to look at alternatives like environment variables or the registry?
You can use environment variables or the registry to achieve it.
environment variables
you could use environment variables like this:
<UsingTask TaskName="MyTask" AssemblyFile="$(yourenvironmentvariablesname)MyDLL.dll" />
For more information, please refer to:
https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-environment-variables-in-a-build
registry
You could use registry like this:
<UsingTask TaskName="MyTask" AssemblyFile="$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework#DbgManagedDebugger)MyDLL.dll" />
Note: please change related registry path as you want.
https://blogs.msdn.microsoft.com/msbuild/2007/05/04/new-registry-syntax-in-msbuild-v3-5/

The solution that made sense for me was to create a wizard and to set the path to the extension install location in the replacements dictionary and use the replacement in the template with the UsingTask.
public class ProjectLocationWizard : IWizard
{
public void BeforeOpeningFile(ProjectItem projectItem)
{
}
public void ProjectFinishedGenerating(Project project)
{
}
public void ProjectItemFinishedGenerating(ProjectItem projectItem)
{
}
public void RunFinished()
{
}
public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
var wizardDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
replacementsDictionary.Add("$installlocation$", wizardDirectory);
}
public bool ShouldAddProjectItem(string filePath)
{
return true;
}
}
<UsingTask AssemblyFile="$installlocation$\MyTask.dll" TaskName="MyTask" />
Although the wizard docs say to sign the wizard assembly, I did not, and it works fine without.

Related

Inject EmbeddedResource created by MSBuild Task

I have a custom MSBuild Task that generates a file based on files that have no build action. The generated files need to be embedded into the final assembly. The task looks something like this:
public class MyTask : Task
{
public string OutputDirectory { get; set; }
public string[] NoneIncluded { get; set; }
private IEnumerable<ITaskItem> _generatedFiles;
[Output]
public ITaskItem[] GeneratedFiles => _generatedFiles.ToArray();
public override bool Execute()
{
_generatedCssFiles = new List<ITaskItem>();
foreach(var item in NoneIncluded)
{
if(someCondition)
{
var contents = DoFoo(item);
var outputPath = Path.Combine(OutputDirectory, $"{item}.txt");
File.WriteAllText(outputPath, contents);
_generatedFiles.Add(new TaskItem(ProjectCollection.Escape(outputFile)));
}
}
}
}
In my targets file, I then have a target defined like the following:
<PropertyGroup>
<CoreCompileDependsOn>MyTarget;$(CoreCompileDependsOn);</CoreCompileDependsOn>
</PropertyGroup>
<Target Name="MyTarget"
BeforeTargets="CoreCompile;Build">
<MyTask OutputDirectory="$(IntermediateOutputPath)"
NoneIncluded="#(None)">
<Output ItemName="FilesWrite"
TaskParameter="GeneratedFiles"/>
<Output ItemName="EmbeddedResource"
TaskParameter="GeneratedFiles" />
</MyTask>
</Target>
I ultimately end up with two issues that I can't seem to resolve:
Although the files are generated prior to the compile task, it isn't embedded into the assembly, unless I rebuild the project without cleaning the outputs. On the subsequent build the file is embedded.
If I generate the files in the IntermediateOutputPath, the embedded resource id includes that path. So instead of MyProject.SomeResource.txt I get MyProject.obj.netstandard2._0.SomeResource.txt
Note:
- If I replace the Path.Combine and simply generate the output file in the project, it fixes the issue of the resource id, but not the first issue with it not being embedded on the first compile.
How can I ensure the my generated files are embedded on the first compilation, and that I can generate them in the IntermediateOutputPath rather than the Project directory with a resource id as if it were in the project.
You need to run your target earlier in the pipeline. CoreCompile is too late.
You can add the following to your .csproj file. This target will run before ResolveReferences and add Image.png as an embedded resource.
<Target Name="IncludeDynamicResources" BeforeTargets="ResolveReferences">
<ItemGroup>
<EmbeddedResource Include="Image.png">
<Type>Non-Resx</Type>
</EmbeddedResource>
</ItemGroup>
</Target>
Your second issue can be solved by simply setting LogicalName metadata to the created task item.
TaskItem taskItem = new TaskItem(ProjectCollection.Escape(outputFile));
taskItem.SetMetadata("LogicalName", $"{AssemblyName}.{item}.txt");
_generatedFiles.Add(taskItem);
Now to access the assembly name inside your task just add AssemblyName string property to MyTask and AssemblyName="$(AssemblyName)" to your targets. This should produce the desired result.

What is causing the error that swagger is already in the route collection for Web API 2?

I installed Swagger in my ASP.Net MVC Core project and it is documenting my API beautifully.
My co-worker asked me to install it in a full framework 4.6.1 project so I've done the following.
In Package Console Manager run:
Install-Package Swashbuckle
To get your Test Web API controller working:
1) Comment this out in the WebApi.config:
// config.SuppressDefaultHostAuthentication();
// config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Now this URL:
http://localhost:33515/api/Test
brings back XML:
<ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<string>value1</string>
<string>value2</string>
</ArrayOfstring>
In Global.asax Register() I call:
SwaggerConfig.Register();
In AppStart.Swagger.Config Register() I put:
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1.0", "HRSA CHAFS");
c.IncludeXmlComments(GetXmlCommentsPath());
})
.EnableSwaggerUi(c =>
{});
}
private static string GetXmlCommentsPath()
{
var path = String.Format(#"{0}bin\Services.XML", AppDomain.CurrentDomain.BaseDirectory);
return path;
}
}
Now I get this error:
"A route named 'swagger_docsswagger/docs/{apiVersion}' is already in the route collection. Route names must be unique."
I've been stuck on this for hours.
How do you get rid of this?
This can happen when you re-name your .NET assembly. A DLL with the previous assembly name will be present in your bin folder. This causes the swagger error.
Delete your bin folder and re-build your solution.
This resolves the swagger error.
Swagger config uses pre-application start hook, so you don't need to call SwaggerConfig.Register() explicitly. Otherwise Register method is called twice.
[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
in my case i added another project as refrence and that other project has swagger too.
i remove that refrence and move needed code to new project.
I solved the problem by deleting the SwaggerConfig.cs file from the App_Start folder as I had already created it manually.
Take a look at this link, here also has more useful information:
A route named 'DefaultApi' is already in the route collection error
In my experience the error occurs when you add reference to another project and that project is a service and it occurs on the SwaggerConfig of the referenced project. Removing project reference usually solve the problem, if you need to share classes I suggest you to create a specific project as Class Library and add its reference to both your services

Strongly typed configuration asp core

I am trying to create a strongly typed config section but struggling. Examples show that I can have a POCO and simply have an entry in my json this should automatically resolve.
This is what I have in ConfigureServices(). Please note, the configuration is IConfigurationRoot:
public void ConfigureServices(IServiceCollection services)
{
services
.AddOptions()
.AddMvcCore()
.AddJsonFormatters();
services.Configure<MySettings>(this.configuration.GetSection("MySettings"));
}
This is my POCO
public class MySettings
{
public string Foo { get; set; }
}
I get a compiler error Error:(41, 44) : Argument 2: cannot convert from 'Microsoft.Extensions.Configuration.IConfigurationSection' to 'System.Action<MySettings>'.
The JSON config:
{
"MySettings": {
"Foo": "hello world"
}
}
Clearly, I am doing something silly but unsure what this could be. All sources on the web suggest this "should" work.
If further info is required then I can provide that.
You are missing
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0"
in your project.json file.
(The version may be different in your specific case)
A more complete answer is that you need to add the following nuget package to your ASP Core Project if you want to configure the strongly typed config in that way.
Microsoft.Extensions.Options.ConfigurationExtensions
The extension methods contained in the package will allow you to configure the strongly typed configuration the way you want to and the way most tutorials show.
services.Configure<MyOptions>(Configuration);
Alternatively, you could add another binder package:
Microsoft.Extensions.Configuration.Binder
Configuration would then look something like this:
services.AddOptions();
services.Configure<MyOptions>(x => Configuration.Bind(x));
This is the downside of having so many modular packaged up extensions. It gets easy to lose track of where functionality exists.

in asp.net 5 is it possible to store and read custom files in approot instead of wwwroot?

when you deploy an asp.net5/mvc6 app there is the wwwroot folder where web assets like css, js, images belong, and there is approot folder where packages and source code belong.
It seems that classes in the Microsoft.Framework.Configuration namespace for example must be able to read files from below approot since that is where config.json files would live.
What I want to know is, is it possible to store and read custom files of my own in approot? and if so how?
For example I'm not using Entity Framework so I need a place to put sql install and upgrade scripts and would prefer not to put them beneath wwwroot. I also have custom configuration files for things like navigation sitemap that I would rather not put below wwwroot if it is possible to put them elsewhere such as approot.
I know I can access files below wwwroot using IHostingEnvironment env.MapPath("~/somefileinwwwrootfoilder.json")
Is there a similar way to access files under approot?
The accepted answer is correct, but since a ASP.NET Core 1.0 release a few things have changed so I thought I'd write a new clear things up a bit.
What I did was create a folder in my project called AppData. You can call it anything you like.
Note: it's not in wwwroot because we want to keep this data private.
Next, you can use IHostingEnvironment to get access to the folder path. This interface can be injected as a dependency into some kind of helper service and what you end up with is something like this:
public class AppDataHelper
{
private readonly IHostingEnvironment _hostingEnvironment;
private const string _appDataFolder = "AppData";
public AppDataHelper(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public async Task<string> ReadAllTextAsync(string relativePath)
{
var path = Path.Combine(_hostingEnvironment.ContentRootPath, _appDataFolder, relativePath);
using (var reader = File.OpenText(path))
{
return await reader.ReadToEndAsync();
}
}
}
Additionally, to get things to deploy correctly I had to add the AppData folder to the publishOptions include list in project.json.
As mentioned in the comments, to deploy AppData folder correctly in ASP.NET MVC Core 2 (using *.csproj file, instead of project.json), syntax is as follows:
<ItemGroup>
<None Include="AppData\*" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
Yes, it is possible. Just get the path to your app folder and the pass it to configuration or whoever else needs it:
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var wwwrootRoot = env.WebRootPath;
var appRoot = appEnv.ApplicationBasePath;

How do I specify my resourcesXml for Glassfish as a resource itself, and not a local file?

I want to put all my configuration files (arquillian.xml, glassfish-resources.xml,test-persistence.xml) in a library and just reference that when i want to run a test in a project. But it seems the resourceXml only allows for a file-path and not a classpath: variable.
Is there a way to do that?
My configuration is currently based upon the arquillian-persistence-tutorial example project. I actually have no special configuration which is need. Just a in-memory db etc. etc.
I fixed it by extending the Arquillian class, and writing the resource to a local file (in the target folder) before any test.
Now in a domain project i only need to do this in a unit test:
public class MyEntityRepositoryImplTest extends BaseArquillianTest {
#Deployment
public static Archive<?> createDeployment() {
return create().//
addClass(MyEntityRepositoryImpl.class).//
addClass(MyEntityDaoImpl.class).//
addClass(MockSomeRepositoryImpl.class).//
addClass(MyEntityJpa.class);
}
#EJB
private MyEntityRepository myEntityRepository;
#Test
public void mMytest () {
}