I have a solution with two projects, a master and a slave. Both these projects are WinForms Applications.
There's always one instance of the master running and multiple instances of the slaves. The slaves are started by the master.
Right now I have the executable name of the slave hardcoded into the master's code, which works fine.
However, I want to be able to get the executable name of the slave without hardcoding it, but by getting it through its reference.
Is this possible, and how should I go about doing this?
This is what I currently have:
ProcessStartInfo startInfo = new ProcessStartInfo("Slave.exe") { Arguments = Args };
Process.Start(startInfo);
But I would like to replace "Slave.exe" with something dynamical. I have added an assembly reference to the slave project, having namespace Slave.
Thanks to #Postlagerkarte's suggestion, I solved my problem by using post build events. I have 4 different slave processes, let's call them SlaveProcessA to SlaveProcessD. First thing I did was make a class which can hold the executable names, and (de)serialize them:
public class ExeNames {
[XmlIgnore]
public bool IsLoaded {get; private set;}
[XmlIgnore]
const string Filename = "exenames.xml";
public string SlaveAExe {get; set;}
public string SlaveBExe {get; set;}
public string SlaveDExe {get; set;}
public string SlaveCExe {get; set;}
public void Load() {
var serializer = new XmlSerializer(typeof(ExeNames));
using (StreamReader reader = new StreamReader(Filename)) {
ExeNames End = serializer.Deserialize(reader) as ExeNames;
if (End == null) return;
SlaveAExe = End.SlaveAExe;
SlaveBExe = End.SlaveBExe;
SlaveDExe = End.SlaveDExe;
SlaveCExe = End.SlaveCExe;
IsLoaded = true;
}
}
public void Save() {
var serializer = new XmlSerializer(typeof(ExeNames));
using (StreamWriter writer = new StreamWriter(Filename)) {
serializer.Serialize(writer, this);
}
}
}
After that, I created a new project called ExeNameSaver. This program takes 2 parameters: the code of the process, and the executable name:
namespace ExeNameSaver {
class Program {
static void Main(string[] args) {
if (args.Length < 2) throw new ArgumentException("2 Parameters are required");
var Code = args[0];
var Exe = args[1];
ExeNames.ExeNames Exn = new ExeNames.ExeNames();
try {
Exn.Load();
}
catch {
Console.WriteLine("Starting new file...");
}
switch (Code) {
case "SlaveA":
Exn.SlaveAExe = Exe;
break;
case "SlaveB":
Exn.SlaveBExe = Exe;
break;
case "SlaveC":
Exn.SlaveCExe = Exe;
break;
case "SlaveD":
Exn.SlaveDExe = Exe;
break;
default:
Console.WriteLine("Invalid process code: " + Code);
break;
}
Exn.Save();
}
}
}
Then, I make sure that the ExeNameSaver is built first by altering the dependencies. Now I can use the ExeNameSaver program to save my executable names to an XML-file, by putting the following in the Post-build event command line of SlaveProcessA:
$(OutDir)ExeNameSaver.exe SlaveA $(TargetFileName)
And similarly for the other 3 slaves.
$(OutDir)ExeNameSaver.exe SlaveB $(TargetFileName)
...
$(OutDir)ExeNameSaver.exe SlaveC $(TargetFileName)
...
$(OutDir)ExeNameSaver.exe SlaveD $(TargetFileName)
Now after each build, the exenames.xml file will contain the executable names of my 4 processes. I can then load this XML using the same ExeNames class in my master program and use their names from there.
Related
I'm a beginner of roslyn, so I tried to start learning it by making a very simple console application, which is introduced in the famous tutorial site. (https://riptutorial.com/roslyn/example/16545/introspective-analysis-of-an-analyzer-in-csharp), and it didn't work well.
The Cosole Application I made is of .NET Framework (target Framework version is 4.7.2), and not of .NET Core nor .NET standard.
I added the NuGet package Microsoft.CodeAnalysis, and Microsoft.CodeAnalysis.Workspaces.MSBuild, then wrote a simple code as I show below.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Linq;
namespace SimpleRoslynConsole
{
class Program
{
static void Main(string[] args)
{
// Declaring a variable with the current project file path.
// *** You have to change this path to fit your development environment.
const string projectPath =
#"C:\Users\[MyName]\Source\Repos\RoslynTrialConsole01\RoslynTrialConsole01.csproj";
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
// [**1]Getting the compilation.
var compilation = project.GetCompilationAsync().Result;
// [**2]As this is a simple single file program, the first syntax tree will be the current file.
var syntaxTree = compilation.SyntaxTrees.FirstOrDefault();
if (syntaxTree != null)
{
var rootSyntaxNode = syntaxTree.GetRootAsync().Result;
var firstLocalVariablesDeclaration = rootSyntaxNode.DescendantNodesAndSelf()
.OfType<LocalDeclarationStatementSyntax>().First();
var firstVariable = firstLocalVariablesDeclaration.Declaration.Variables.First();
var variableInitializer = firstVariable.Initializer.Value.GetFirstToken().ValueText;
Console.WriteLine(variableInitializer);
}
else
{
Console.WriteLine("Could not get SyntaxTrees from this projects.");
}
Console.WriteLine("Hit any key.");
Console.ReadKey();
}
}
}
My problem is that, SyntaxTrees property of Compilation object returns null in [**2]mark. Naturally, following FirstOrDefault method returns null.
I've tried several other code. I found I could get SyntaxTree from CSharp code text, by using CSharpSyntaxTree.ParseText method. But I couldn't get any from source code, by the sequence of
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
var compilation = project.GetCompilationAsync().Result;
What I'd like to know is if I miss something to get Syntax information from source code by using above process.
I'll appreciate someone give me a good advice.
I think the issue is that .net framework projects have their source files paths within their .csproj. And opening project works right away.
For .net core project you have no such information and, maybe, this is why Workspace instance doesn't know what to load and so loads nothing.
At least specifying .cs files as added documents does the trick. Try to apply this:
static class ProjectExtensions
{
public static Project AddDocuments(this Project project, IEnumerable<string> files)
{
foreach (string file in files)
{
project = project.AddDocument(file, File.ReadAllText(file)).Project;
}
return project;
}
private static IEnumerable<string> GetAllSourceFiles(string directoryPath)
{
var res = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);
return res;
}
public static Project WithAllSourceFiles(this Project project)
{
string projectDirectory = Directory.GetParent(project.FilePath).FullName;
var files = GetAllSourceFiles(projectDirectory);
var newProject = project.AddDocuments(files);
return newProject;
}
}
Method WithAllsourceFiles will return you the project, compilation of which will in its turn have all syntax trees you would expect of it, as you would have in Visual Studio
MsBuildWorkspace won't work correctly unless you have all the same redirects in your app's app.config file that msbuild.exe.config has in it. Without the redirects, it's probably failing to load the msbuild libraries. You need to find the msbuild.exe.config file that is on your system and copy the <assemblyBinding> elements related to Microsoft.Build assemblies into your app.config. Make sure you place them under the correct elements configuration/runtime.
I searched various sample programs on the net and found the most reliable and safest method. The solution is to create a static method which returns SyntaxTrees in designated File as follow.
private static Compilation CreateTestCompilation()
{
var found = false;
var di = new DirectoryInfo(Environment.CurrentDirectory);
var fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
while ((fi == null) || (di.Parent == null))
{
di = new DirectoryInfo(di.Parent.FullName);
fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
if (fi != null)
{
found = true;
break;
}
}
if (!found)
{
return null;
}
var targetPath = di.FullName + #"\Program.cs";
var targetText = File.ReadAllText(targetPath);
var targetTree =
CSharpSyntaxTree.ParseText(targetText)
.WithFilePath(targetPath);
var target2Path = di.FullName + #"\TypeInferenceRewriter.cs";
var target2Text = File.ReadAllText(target2Path);
var target2Tree =
CSharpSyntaxTree.ParseText(target2Text)
.WithFilePath(target2Path);
SyntaxTree[] sourceTrees = { programTree, target2Tree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(
OutputKind.ConsoleApplication));
}
And the caller program will be like this.
static void Main(string[] args)
{
var test = CreateTestCompilation();
if (test == null)
{
return;
}
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
Console.WriteLine(souceTree.ToFullString());
}
}
Of course, many improvements are needed to put it to practical use.
I've read the tutorial and I'm able to generate the .cs file but it doesn't include any of my service or rpc definitions.
I've added protoc to my PATH and from inside the project directory.
protoc project1.proto --csharp_out="C:\output" --plugin=protoc-gen-grpc="c:\Users\me\.nuget\packages\grpc.tools\1.8.0\tools\windows_x64\grpc_csharp_plugin.exe"
No errors output in console
You need to add the --grpc_out command line option, e.g. add
--grpc_out="C:\output\"
Note that it won't write any files if you don't have any services.
Here's a complete example. From a root directory, create:
An empty output directory
A tools directory with protoc.exe and grpc_csharp_plugin.exe
A protos directory with test.proto as shown below:
test.proto:
syntax = "proto3";
service StackOverflowService {
rpc GetAnswer(Question) returns (Answer);
}
message Question {
string text = 1;
string user = 2;
repeated string tags = 3;
}
message Answer {
string text = 1;
string user = 2;
}
Then run (all on one line; I've broken it just for readability here):
tools\protoc.exe -I protos protos\test.proto --csharp_out=output
--grpc_out=output --plugin=protoc-gen-grpc=tools\grpc_csharp_plugin.exe
In the output directory, you'll find Test.cs and TestGrpc.cs
Just an idle comment here for other that find this, the documentation about this is terribly out of date and just flat out wrong.
Installing Grpc.Tools does not install anything in a packages folder; that is legacy behaviour which is no longer true even on windows.
When you install Grpc.Tools it will be hidden away in your local package cache, which you can see by calling:
$ dotnet nuget locals all --list
info : http-cache: /Users/doug/.local/share/NuGet/v3-cache
info : global-packages: /Users/doug/.nuget/packages/
info : temp: /var/folders/xx/s2hnzbrj3yn4hp1bg8q9gb_m0000gn/T/NuGetScratch
The binaries you want will be in one of these folders.
The easiest way to do this is to download the Grpc.Tools package directly from nuget, and install it locally.
I've hacked up this little helper script to do that, which works on windows/mac/linux, which may ease the difficulty of getting starting with this for others:
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Mono.Unix;
namespace BuildProtocol
{
public class Program
{
private const string ToolsUrl = "https://www.nuget.org/api/v2/package/Grpc.Tools/";
private const string Service = "Greeter";
private static string ProtocolPath = Path.Combine("..", "protos");
private static string Protocol = Path.Combine(ProtocolPath, "helloworld.proto");
private static string Output = Path.Combine("..", "Greeter");
public static void Main(string[] args)
{
RequireTools().Wait();
var protoc = ProtocPath();
var plugin = ProtocPluginPath();
Console.WriteLine($"Using: {protoc}");
Console.WriteLine($"Using: {plugin}");
var command = new string[]
{
$"-I{ProtocolPath}",
$"--csharp_out={Output}",
$"--grpc_out={Output}",
$"--plugin=protoc-gen-grpc=\"{plugin}\"",
Protocol,
};
Console.WriteLine($"Exec: {protoc} {string.Join(' ', command)}");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
UseShellExecute = false,
FileName = protoc,
Arguments = string.Join(' ', command)
}
};
process.Start();
process.WaitForExit();
Console.WriteLine($"Completed status: {process.ExitCode}");
}
public static async Task RequireTools()
{
if (!Directory.Exists("Tools"))
{
Console.WriteLine("No local tools found, downloading binaries from nuget...");
Directory.CreateDirectory("Tools");
await DownloadTools();
ExtractTools();
}
}
private static void ExtractTools()
{
ZipFile.ExtractToDirectory(Path.Combine("Tools", "tools.zip"), Path.Combine("Tools", "bin"));
}
private static async Task DownloadTools()
{
using (var client = new HttpClient())
{
Console.WriteLine($"Fetching: {ToolsUrl}");
using (var result = await client.GetAsync(ToolsUrl))
{
if (!result.IsSuccessStatusCode) throw new Exception($"Unable to download tools ({result.StatusCode}), check URL");
var localArchive = Path.Combine("Tools", "tools.zip");
Console.WriteLine($"Saving to: {localArchive}");
File.WriteAllBytes(localArchive, await result.Content.ReadAsByteArrayAsync());
}
}
}
private static string ProtocPath()
{
var path = Path.Combine("Tools", "bin", "tools", DetermineArch(), "protoc");
RequireExecutablePermission(path);
return WithExeExtensionIfRequired(path);
}
private static string ProtocPluginPath()
{
var path = Path.Combine("Tools", "bin", "tools", DetermineArch(), "grpc_csharp_plugin");
RequireExecutablePermission(path);
return WithExeExtensionIfRequired(path);
}
private static void RequireExecutablePermission(string path)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
Console.WriteLine($"Ensuring +x on {path}");
var unixFileInfo = new UnixFileInfo(path);
unixFileInfo.FileAccessPermissions = FileAccessPermissions.UserRead | FileAccessPermissions.UserWrite | FileAccessPermissions.UserExecute;
}
private static string WithExeExtensionIfRequired(string path)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
path += ".exe";
}
return path;
}
private static string DetermineArch()
{
var arch = RuntimeInformation.OSArchitecture;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return WithArch("windows_", arch);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return WithArch("macosx_", arch);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return WithArch("linux_", arch);
}
throw new Exception("Unable to determine runtime");
}
private static string WithArch(string platform, Architecture arch)
{
switch (arch)
{
case Architecture.X64:
return $"{platform}x86";
case Architecture.X86:
return $"{platform}x64";
default:
throw new ArgumentOutOfRangeException(nameof(arch), arch, null);
}
}
}
}
the following approach helped me :
Create a gRPC client and server in ASP.NET Core
in project, where .proto file located, edit the .csproj file
<ItemGroup>
....
<Protobuf Include="Shipping.proto" GrpcServices="Server" />
</ItemGroup>
rebuild the project, the all necessary .cs files will be added automaticaly
\obj\Debug\[TARGET_FRAMEWORK]\Shipping.cs
\obj\Debug\[TARGET_FRAMEWORK]\ShippingGrpc.cs
I want to add a Product Version to a Form.[assembly: AssemblyVersion("1.0.*")]
string version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
This is the solution I found, and which will work. But the Version will be like "1.0.6262.26540".
Can I change the Rule or can I get the Publish Version which Visual Studio generates programmatically?
You could use ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString(). However, this will only work if you are running a version of your program that was installed by the ClickOnce publisher installer (ApplicationDeployment.IsNetworkDeployed returns true).
When you start the compiled assembly directly (e.g. during debugging), you will get an InvalidDeploymentException when trying to access the CurrentDeployment property. To safeguard against this, you can use something like this:
string CurrentVersion
{
get
{
return ApplicationDeployment.IsNetworkDeployed
? ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString()
: "1.0.0.0"; // Fallback version string, or retrieve from assembly as in your question
}
}
If you are not using the ClickOnce Publish function to distribute your software I am not sure that you can expect to access the "Publish Version".
using System;
using System.IO;
using System.Linq;
namespace MyAssemblyInfoPatcher
{
internal class Program
{
static void Main(string[] args)
{
if (args.Length > 0)
{
string path = args[0].ToString();
Console.WriteLine(string.Format("Current App version is set to: {0}", path));
string now_date = DateTime.Now.ToString("yyyy.MM.dd.HHmm");
if (File.Exists(path))
{
string _AssemblyVersion = string.Empty;
string _AssemblyFileVersion = string.Empty;
var lines = File.ReadLines(string.Format(path));
for (int i = 0; i < lines.Count(); i++)
{
if (lines.ElementAt(i).ToString().StartsWith("[assembly: AssemblyVersion"))
{
_AssemblyVersion = lines.ElementAt(i).ToString();
}
else if (lines.ElementAt(i).ToString().StartsWith("[assembly: AssemblyFileVersion"))
{
_AssemblyFileVersion = lines.ElementAt(i).ToString();
}
}
string _replace_assembly = File.ReadAllText(path);
if (_AssemblyVersion != string.Empty)
{
_replace_assembly = _replace_assembly.Replace(_AssemblyVersion, string.Format("[assembly: AssemblyVersion(\"{0}\")]", now_date));
}
if (_AssemblyFileVersion != string.Empty)
{
_replace_assembly = _replace_assembly.Replace(_AssemblyFileVersion, string.Format("[assembly: AssemblyFileVersion(\"{0}\")]", now_date));
}
File.WriteAllText(path, _replace_assembly);
}
}
}
}
}
Above the programs code, you can create a console application and in Project Properties > Build Events, add a "Pre-build event command line" like this: "D:\SomePath\MyAssemblyInfoPatcher.exe" "$(ProjectDir)Properties\AssemblyInfo.cs"
I need to obtain the number of test iterations for a load test from within a load test plugin, where I have an instance of a LoadTest object. I've searched the LoadTest object's properties and it feels like there is a lot missing compared to the treeview editor that is normally used to configure a load test.
I'm already defining the number of test iterations again as a Context parameter and passing that through to my web test, but this feels like a hack because I'm duplicating data.
class MyLoadTestPlugin : ILoadTestPlugin
{
private LoadTest loadTest;
public void Initialize(LoadTest test)
{
loadTest = test;
loadTest.TestStarting += (_, e) =>
{
// Get # of Test Iterations in load test here,
// "loadTest" object does not have nearly as
// many properties as it should, compared to
// the tree view editor.
};
}
}
Use the LoadTestPlugin to read the .loadtest file which is a XML file. Here is an example to read the TotalIterations in the .loadtest file.
using System;
using Microsoft.VisualStudio.TestTools.LoadTesting;
using System.IO;
using System.Xml;
namespace LoadTest
{
public class LoadTestPluginImpl : ILoadTestPlugin
{
LoadTest mLoadTest;
static int TotalIterations;
public void Initialize(LoadTest loadTest)
{
mLoadTest = loadTest;
//connect to the TestStarting event.
mLoadTest.TestStarting += new EventHandler<TestStartingEventArgs>(mLoadTest_TestStarting);
ReadTestConfig();
}
void mLoadTest_TestStarting(object sender, TestStartingEventArgs e)
{
//When the test starts, copy the load test context parameters to
//the test context parameters
foreach (string key in mLoadTest.Context.Keys)
{
e.TestContextProperties.Add(key, mLoadTest.Context[key]);
}
//add the CurrentTestIteration to the TestContext
e.TestContextProperties.Add("TestIterationNumber", e.TestIterationNumber);
//add the TotalIterations to the TestContext and access from the Unit Test.
e.TestContextProperties.Add("TotalIterations", TotalIterations);
}
void ReadTestConfig()
{
string filePath = Path.Combine(Environment.CurrentDirectory, mLoadTest.Name + ".loadtest");
if (File.Exists(filePath))
{
string runSettings = mLoadTest.RunSettings.Name;
XmlDocument xdoc = new XmlDocument();
xdoc.Load(filePath);
XmlElement root = xdoc.DocumentElement;
string xmlNameSpace = root.GetAttribute("xmlns");
XmlNamespaceManager xmlMgr = new XmlNamespaceManager(xdoc.NameTable);
if (!string.IsNullOrWhiteSpace(xmlNameSpace))
{
xmlMgr.AddNamespace("lt", xmlNameSpace);
}
var nodeRunSettings = xdoc.SelectSingleNode(string.Format("//lt:LoadTest/lt:RunConfigurations/lt:RunConfiguration[#Name='{0}']", runSettings), xmlMgr);
//var nodeRunSettings = xdoc.SelectSingleNode(string.Format("//lt:LoadTest", runSettings), xmlMgr);
if (nodeRunSettings != null)
{
TotalIterations = Convert.ToInt32(nodeRunSettings.Attributes["TestIterations"].Value);
}
}
}
}
}
Similarly you can read the other values.
A web test has the current iteration number in webTest.Context.WebTestIteration (and also as a context parameter named $WebTestIteration).
A LoadTest has access to the current iteration number in the TestStartingEventArgs object:
loadTest.TestStarting += ( (sender, e) =>
{
int iteration = e.TestIterationNumber;
};
To prove to myself that these values are the same and that there is no unexpected behaviour like numbers being reused across different scenarios, I (EDIT:re-) wrote these plugins, and it checks out.
(Thanks to #AdrianHHH for pointing out that the previous code was not complete)
public class LoadTestIteration : ILoadTestPlugin
{
List<int> usedTestIterationNumbers = new List<int>();
public void Initialize(LoadTest loadTest)
{
loadTest.TestStarting += (sender, e) =>
{
e.TestContextProperties["$LoadTest.TestIterationNumber"] = e.TestIterationNumber;
System.Diagnostics.Debug.Assert(!usedTestIterationNumbers.Contains(e.TestIterationNumber), "Duplicate LoadTest TestIterationNumber: " + e.TestIterationNumber);
usedTestIterationNumbers.Add(e.TestIterationNumber);
};
}
}
public class TestWebTestIteration : WebTestPlugin
{
public override void PreWebTest(object sender, PreWebTestEventArgs e)
{
int lti = (int)e.WebTest.Context["$LoadTest.TestIterationNumber"];
int wti = e.WebTest.Context.WebTestIteration;
System.Diagnostics.Debug.Assert(lti == wti, String.Format("$LoadTestIteration {0} differs from $WebTestIteration {1}", lti, wti));
}
}
Very first time I am developing a utility using T4 to generate .aspx, .aspx.cs files as well as stored procedures. For that I need to pass some parameters like tablename, pagename etc. from windows form to the T4 template. And I have written templates and also a customHost as given http://msdn.microsoft.com/en-us/library/bb126579.aspx to call from windows form. Now I can call T4 template from windows form, but I don't know how to pass parameters to T4 template. I have tried to create property in template and call template from Win form but it gives me error on this line "var sessionHost = (ITextTemplatingSessionHost) this.Host;" because I use CustomHost. Please help me out.
Here is the code sample for T4 Template
Text Template Host Test
<## template debug="true" #>
<## parameter name="MyParameter" type="System.String" #>
<# for (int i=0; i<3; i++)
{
WriteLine("This is a test");
}
#>
Parameter in statement block: <# Write(MyParameter); #>
CustomHost.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using System.CodeDom.Compiler;
using System.IO;
namespace FormGenerator
{
class CustomHost : ITextTemplatingEngineHost
{
//the path and file name of the text template that is being processed
//---------------------------------------------------------------------
internal string TemplateFileValue;
public string TemplateFile
{
get { return TemplateFileValue; }
}
//This will be the extension of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private string fileExtensionValue = ".txt";
public string FileExtension
{
get { return fileExtensionValue; }
}
//This will be the encoding of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private Encoding fileEncodingValue = Encoding.UTF8;
public Encoding FileEncoding
{
get { return fileEncodingValue; }
}
//These are the errors that occur when the engine processes a template.
//The engine passes the errors to the host when it is done processing,
//and the host can decide how to display them. For example, the host
//can display the errors in the UI or write them to a file.
//---------------------------------------------------------------------
private CompilerErrorCollection errorsValue;
public CompilerErrorCollection Errors
{
get { return errorsValue; }
}
//The host can provide standard assembly references.
//The engine will use these references when compiling and
//executing the generated transformation class.
//--------------------------------------------------------------
public IList<string> StandardAssemblyReferences
{
get
{
return new string[]
{
//If this host searches standard paths and the GAC,
//we can specify the assembly name like this.
//---------------------------------------------------------
//"System"
//Because this host only resolves assemblies from the
//fully qualified path and name of the assembly,
//this is a quick way to get the code to give us the
//fully qualified path and name of the System assembly.
//---------------------------------------------------------
typeof(System.Uri).Assembly.Location
};
}
}
//The host can provide standard imports or using statements.
//The engine will add these statements to the generated
//transformation class.
//--------------------------------------------------------------
public IList<string> StandardImports
{
get
{
return new string[]
{
"System"
};
}
}
//The engine calls this method based on the optional include directive
//if the user has specified it in the text template.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
//The included text is returned in the context parameter.
//If the host searches the registry for the location of include files,
//or if the host searches multiple locations by default, the host can
//return the final path of the include file in the location parameter.
//---------------------------------------------------------------------
public bool LoadIncludeText(string requestFileName, out string content, out string location)
{
content = System.String.Empty;
location = System.String.Empty;
//If the argument is the fully qualified path of an existing file,
//then we are done.
//----------------------------------------------------------------
if (File.Exists(requestFileName))
{
content = File.ReadAllText(requestFileName);
return true;
}
//This can be customized to search specific paths for the file.
//This can be customized to accept paths to search as command line
//arguments.
//----------------------------------------------------------------
else
{
return false;
}
}
//Called by the Engine to enquire about
//the processing options you require.
//If you recognize that option, return an
//appropriate value.
//Otherwise, pass back NULL.
//--------------------------------------------------------------------
public object GetHostOption(string optionName)
{
object returnObject;
switch (optionName)
{
case "CacheAssemblies":
returnObject = true;
break;
default:
returnObject = null;
break;
}
return returnObject;
}
//The engine calls this method to resolve assembly references used in
//the generated transformation class project and for the optional
//assembly directive if the user has specified it in the text template.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public string ResolveAssemblyReference(string assemblyReference)
{
//If the argument is the fully qualified path of an existing file,
//then we are done. (This does not do any work.)
//----------------------------------------------------------------
if (File.Exists(assemblyReference))
{
return assemblyReference;
}
//Maybe the assembly is in the same folder as the text template that
//called the directive.
//----------------------------------------------------------------
string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
if (File.Exists(candidate))
{
return candidate;
}
//This can be customized to search specific paths for the file
//or to search the GAC.
//----------------------------------------------------------------
//This can be customized to accept paths to search as command line
//arguments.
//----------------------------------------------------------------
//If we cannot do better, return the original file name.
return "";
}
//The engine calls this method based on the directives the user has
//specified in the text template.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public Type ResolveDirectiveProcessor(string processorName)
{
//This host will not resolve any specific processors.
//Check the processor name, and if it is the name of a processor the
//host wants to support, return the type of the processor.
//---------------------------------------------------------------------
if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase) == 0)
{
//return typeof();
}
//This can be customized to search specific paths for the file
//or to search the GAC
//If the directive processor cannot be found, throw an error.
throw new Exception("Directive Processor not found");
}
//A directive processor can call this method if a file name does not
//have a path.
//The host can attempt to provide path information by searching
//specific paths for the file and returning the file and path if found.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public string ResolvePath(string fileName)
{
if (fileName == null)
{
throw new ArgumentNullException("the file name cannot be null");
}
//If the argument is the fully qualified path of an existing file,
//then we are done
//----------------------------------------------------------------
if (File.Exists(fileName))
{
return fileName;
}
//Maybe the file is in the same folder as the text template that
//called the directive.
//----------------------------------------------------------------
string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
if (File.Exists(candidate))
{
return candidate;
}
//Look more places.
//----------------------------------------------------------------
//More code can go here...
//If we cannot do better, return the original file name.
return fileName;
}
//If a call to a directive in a text template does not provide a value
//for a required parameter, the directive processor can try to get it
//from the host by calling this method.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
{
if (directiveId == null)
{
throw new ArgumentNullException("the directiveId cannot be null");
}
if (processorName == null)
{
throw new ArgumentNullException("the processorName cannot be null");
}
if (parameterName == null)
{
throw new ArgumentNullException("the parameterName cannot be null");
}
//Code to provide "hard-coded" parameter values goes here.
//This code depends on the directive processors this host will interact with.
//If we cannot do better, return the empty string.
return String.Empty;
}
//The engine calls this method to change the extension of the
//generated text output file based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
public void SetFileExtension(string extension)
{
//The parameter extension has a '.' in front of it already.
//--------------------------------------------------------
fileExtensionValue = extension;
}
//The engine calls this method to change the encoding of the
//generated text output file based on the optional output directive
//if the user specifies it in the text template.
//----------------------------------------------------------------------
public void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
{
fileEncodingValue = encoding;
}
//The engine calls this method when it is done processing a text
//template to pass any errors that occurred to the host.
//The host can decide how to display them.
//---------------------------------------------------------------------
public void LogErrors(CompilerErrorCollection errors)
{
errorsValue = errors;
}
//This is the application domain that is used to compile and run
//the generated transformation class to create the generated text output.
//----------------------------------------------------------------------
public AppDomain ProvideTemplatingAppDomain(string content)
{
//This host will provide a new application domain each time the
//engine processes a text template.
//-------------------------------------------------------------
return AppDomain.CreateDomain("Generation App Domain");
//This could be changed to return the current appdomain, but new
//assemblies are loaded into this AppDomain on a regular basis.
//If the AppDomain lasts too long, it will grow indefintely,
//which might be regarded as a leak.
//This could be customized to cache the application domain for
//a certain number of text template generations (for example, 10).
//This could be customized based on the contents of the text
//template, which are provided as a parameter for that purpose.
}
}
}
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using Microsoft.VisualStudio.TextTemplating;
using System.IO;
using FormGenerator;
using System.CodeDom.Compiler;
namespace FormGenerator
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ProcessTemplate("Path\\TestTemplate.tt");
}
static void ProcessTemplate(string templateFileName)
{
if (templateFileName == null)
{
throw new ArgumentNullException("the file name cannot be null");
}
if (!File.Exists(templateFileName))
{
throw new FileNotFoundException("the file cannot be found");
}
CustomHost host = new CustomHost();
Engine engine = new Engine();
host.TemplateFileValue = templateFileName;
//Read the text template.
string input = File.ReadAllText(templateFileName);
//Transform the text template.
string output = engine.ProcessTemplate(input, host);
string outputFileName = Path.GetFileNameWithoutExtension(templateFileName);
outputFileName = Path.Combine(Path.GetDirectoryName(templateFileName), outputFileName);
outputFileName = outputFileName + "1" + host.FileExtension;
File.WriteAllText(outputFileName, output, host.FileEncoding);
foreach (CompilerError error in host.Errors)
{
Console.WriteLine(error.ToString());
}
}
}
}
I have tried but gives error "Unable to cast object of type 'FormGenerator.CustomHost' to type 'Microsoft.VisualStudio.TextTemplating.ITextTemplatingSessionHost'."
CustomHost Host = new CustomHost();
Host.TemplateFileValue = templateFileName;
string templateFile = Host.ResolvePath("TestTemplate.tt");
string templateContent = File.ReadAllText(templateFile);
TextTemplatingSession session = new TextTemplatingSession();
session["MyParameter"] = "SessionValue";
var sessionHost = (ITextTemplatingSessionHost)Host;
sessionHost.Session = session;
Engine engine = new Engine();
string generatedContent = engine.ProcessTemplate(templateContent, Host);
Please help me out how can I resolve this issue.
Thanks in advance.
You need to make your CustomHos class serializable
[Serializable()]
class CustomCmdLineHost : ITextTemplatingEngineHost
{
}