How to get a property from a task in a TFS build? - msbuild

I made a custom task in my TFS build to examine my project's GlobalAssemblyInfo.cs file in order to extract an attribute (AssemblyInformationalVersion to be exact) in order to use its value in naming a zip file that I make with the build.
<UsingTask TaskName="GetAssemblyInformationalVersion.GetAssemblyInformationalVersionTask"
AssemblyFile="$(MSBuildExtensionsPath)\GetAssemblyInformationalVersion.dll" />
The .cs file for my DLL has these two properties:
[Required]
public String InfoFile { get; set; }
public String InfoVersion { get; set; }
Here is my call to my task:
<GetAssemblyInformationalVersionTask InfoFile="$(Path to file)\GlobalAssemblyInfo.cs" />
My intention is to pass in the assembly info file through the property InfoFile so that I can find what I want (which my C# code does) and set it to the property InfoVersion for me to reference in TFS by running it as a task. In principle, I would use the property InfoVersion to use in naming my zip file. For example,
"Package.$(some form of reference to InfoVersion).zip"
However, I have not been able to find a way to actually accomplish this.
My question is: How can I invoke the get part of my property in my task? It seems like it should be easy, since I have not found anything written about this sort of thing online, but any help will be much appreciated.

Your custom task, GetAssemblyInformationVersionTask, will need to have a property on it of type ITaskItem that is decorated with the [Output] attribute.
public class GetAssemblyInformationVersionTask
{
[Output]
public ITaskItem Version { get; set; }
public override bool Execute()
{
// code to set Version
return true;
}
}
Then you will be able to use it like so:
<GetAssemblyInformationVersionTask InfoFile="$(Path to file)\GlobalAssemblyInfo.cs">
<Output TaskParameter="Version" ItemName="AssemblyVersion" />
</GetAssemblyInformationVersionTask>
AssemblyVersion will be the item variable that will contain the value of the Version property of your task.
If you've not seen it, MSDN Best Practices for Reliable Builds, Part 2 touches on the subject of output parameters. I'll see if I can't find better examples online.
Thomas Ardal has another good sample of [Output] in a custom task here.
HTH,
Z

Related

ASP.NET Core DisplayAttribute Localization

According to the documentation:
The runtime doesn’t look up localized strings for non-validation attributes. In the code above, “Email” (from [Display(Name = "Email")]) will not be localized.
I'm looking for a way to localize text in DisplayAttribute. Any suggestions to do it in a proper way(s)?
You can set the ResourceType on the DisplayAttribute which can be used to localize your text.
Add a resource .resx file to your project e.g. MyResources.resx, and add a resource for your field:
Then reference the name of the field and the MyResources type in your DisplayAttribute
[Display(Name = "RememberMe", ResourceType = typeof(MyResources))]
public bool RememberMe { get; set; }
The localized resource will be pulled through automatically (see the text box)
Having a Central location for all your localization whether in view or dataannotations is the best approach I can think of, and this how I got to work.
In Startup.cs file after you installed nuget packages for localization add the following code
services.AddMvc().AddViewLocalization().AddDataAnnotationsLocalization(options =>
options.DataAnnotationLocalizerProvider = (type, factory) => new StringLocalizer<Resources>(factory));
services.Configure<RequestLocalizationOptions>(options => {
var cultures = new[]
{
new CultureInfo("en"),
new CultureInfo("ar")
};
options.DefaultRequestCulture = new RequestCulture("en", "en");
options.SupportedCultures = cultures;
options.SupportedUICultures = cultures;
});
This way the DataAnnotationLocalizerProvider will be from the Resources.{culture}.rex -( The Resource file must have an access modifier of No code gen)- assuming that no resources will be needed for the default language, and to be able to access the resource file since no code will be generated and empty class with the same name must be created.
and in _ViewImports.cshtml file inject the following
#inject IHtmlLocalizer<Resources> Localizer
by doing this you now have a global variable Localizer to be used in any of the views for localization purposes.
you can find further information on Globalization and localization in ASP.NET Core
For those who struggle (#lucius, #vladislav) with error:
Cannot retrieve property 'Name' because localization failed. Type 'Xxxx.EmployeeResx' is not public or does not contain a public static string property with the name 'FirstName'.
It is caused by access modifier on .resx files which is by default set to Internal (in my case it was No code generation). Change it to public in Access Modifier dropdown in the resource file toolbar.
After that you should be able to see the properties from the resource type:
Also, consider not using special signs in field names as they are a basis for auto-generated C# property names. The field names are converted into C# friendly names and that is why you can end up with inconsistency between name of resource file field and name of auto-generated property. Best to avoid any hyphens - or dots . Underscores _ are fine. You can always look up how the auto-generated properties look like in resource_file_name.Designer.cs class under the related resource file.
Many thanks to Bala Murugan who wrote a good article concerning this topic on Code Digest.
Actually I found an simple solution for the followers. The display name in most of time is used in the label of an input field. So do this if you like:
<label asp-for="Email">#Localizer["Email"]</label>
of course, you can pass the property name by #Html.DisplayNameFor, but most of time, this one already works well.
I have just created a project which demonstrates localization including localization of Display attribute for class properties as well as enums.
The project can be found here https://github.com/feradz/ASPNetCoreLocalization/wiki
The Display attribute has to be localized using the approach prior to ASP.NET Core 1.0. Have a look at the DataAnnotations.resx file in the project.
The Name property of Display cannot contain empty spaces, and special characters.
[Display(Name = "NoSpacesAndSpecialChanractersHere", ResourceType = typeof(Resources.DataAnnotations))]
public string FirstName { get; set; }
ResourceType should be the fully qualified resource class name (i.e. including the name space).

How do I get $(OutDir) programmatically?

I'm writing a custom build task and and would like to access the value of this property programmatically. It is possible? I've looked at all of the members of the Task class and didn't see anything that looked like it would allow me to get the value.
If the custom task can't run without a property, the best practice is to make that property a required input:
[Required]
public string OutDir { get; set; }
Set by:
<MyTask ... OutDir="$(OutDir)" />

Custom Task getting called more than once

I am learning how to use MSBuild recently so I decided to tackle writing my own custom MSBuild task. What I found is that MSBuild is calling my task just fine... but it calls it over and over and over again. It repeats the call to it many times, even though the msbuild project calls it only once.
Here is my project XML:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<PropertyGroup>
<BuildDir>build directory specified here</BuildDir>
.. various other stuff here too
</PropertyGroup>
<Import Project="file1.xml" />
<Import Project="file2.xml" />
<UsingTask TaskName="CopyToBuild.Copy_To_Build"
AssemblyFile="CopyToBuild.dll" />
<Target Name="MyNewCopyTask">
<Copy_To_Build SourceFiles="#(copy_to_build)"
DestinationFolder="%(Destination)"
SkipUnchangedFiles="true"
BuildDirectory="$(BuildDir)" />
</Target>
</Project>
So as you can see I call my Copy_To_Build task only once in the project. I import an xml file that contains items that is passed in to the SourceFiles attribute of my Copy_To_Build task. Everything works great. Except for one thing: the problem is that my Execute method on my custom task gets called more than once.
public class Copy_To_Build : Microsoft.Build.Utilities.Task
{
[Required]
public ITaskItem[] SourceFiles { get; set; }
[Required]
public ITaskItem[] DestinationFolder { get; set; }
public String BuildDirectory { get; set; }
public bool Clean { get; set; }
public bool SkipUnchangedFiles { get; set; }
public override bool Execute()
{
Console.WriteLine("Build Directory: {0}", BuildDirectory);
...
}
}
I know it's getting called more than once because I put a print statement in there which reveals that the function is getting more than once. I expected it to get called only once.
Is it getting called more than once because I have some sort of threading option set? I put in a statement to print the current thread:
Console.WriteLine("Current Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
But that revealed that everything was on the same thread.
Last but not least, here is the command line script I am using to call everything:
#echo off
call "%VS100COMNTOOLS%..\..\VC\vcvarsall.bat" x64
rem set some build properties
set MISC=/nologo /verbosity:Normal
set LOGGING=/fileLogger /fileloggerparameters:LogFile=msbuild_foo.log;Encoding=UTF-8;Verbosity=Normal
set PROPERTY=/property:Platform=x64;Configuration=DebugUnicode;BuildDir=E:\foo
set TARGET=/target:MyNewCopyTask
msbuild %MISC% %LOGGING% %PROPERTY% %TARGET% foo.xml
pause
#echo on
So in summary: Why is my task getting called more than once?
Thanks
<Copy_To_Build SourceFiles="#(copy_to_build)"
DestinationFolder="%(Destination)"
SkipUnchangedFiles="true"
BuildDirectory="$(BuildDir)" />
It will be called once per unique value of the "Destination" metadata. This is called 'batching'. You could either do it like the way it is working right now, or make DestinationFolder property optional, and in case it is not specified, then the task can look for the "Destination" metadata in the items "SourceFiles" and copy the item to that folder.
But the usual way to do it as you have right now, just make DestinationFolder a string.

WCF - Exposing parameterized constructor

I have a WCF DataContract called RecipientDto defined as:
[DataContract]
public class RecipientDto
{
[DataMember]
public string Name
{
get;
private set;
}
[DataMember]
public string EmailAddress
{
get;
private set;
}
public RecipientDto(string name, string emailAddress)
{
Name = name;
EmailAddress = emailAddress;
//Initialize other property here
}
}
I want to have constructor of RecipientDto being exposed to the client as it involve some basic initialization of other properties (not shown here).
Please guide how can I achieve this.
Thank you!
You cannot achieve that unless you share assembly with your DTOs between client and server. Metadata (WSDL + XSD) can describe only data transferred by DTO. They cannot describe any logic defined in DTO on service side.
What you could do is the create a second source file for the RecipientDto class, that contains a second declaration of the class with the "partial" keyword. Add your constructor to it and include that file in your client project using Visual Studio's "Add Link" functionality available on the "Add existing item" dialog. If you only need that constructor on the client then just define that second source file directly in the client project.

MVVM Silverlight - can not assign existing control to a property of collection of custom elements in XAML

I have a class
public class Item
{
public string A { get; set; }
public Control B { get; set; }
}
I'm using MVVM with Silverlight. I have a custom view that is inherited from a standard view. Custom view has public property public ICollection MyItems { get; set; } which should store items described above.
In xaml of my view I have
xxxx.MyItems>
Item A="someText" B="_existingButton" />
Item A="someText2" B="_existingButton2" />
/xxxx.MyItems>
Initialize() method of View fails when trying to assign value for B.
How can I assign a reference to existing element for a custom collection item?
I don't exactly understand what you are trying to achieve, but to help you arrive at a solution, I recommend that you attempt your task in the code behind file first (i.e. in the .xaml.cs) file.
By doing this, you will be given much more informative help from the compiler and intellisense.
Once you've achieved what you wanted in the code behind, then try and implement it in the xaml file.