tag:blogger.com,1999:blog-9259462981013077082024-02-08T00:30:26.301+02:00Vasil Trifonov's BlogProblems and Solutions in .NETVasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.comBlogger18125tag:blogger.com,1999:blog-925946298101307708.post-49166567251378547082015-05-22T12:26:00.000+03:002015-05-22T12:26:25.449+03:00XUnit and MSBuild<div dir="ltr" style="text-align: left;" trbidi="on">
Recently I needed to execute <a href="https://github.com/xunit/xunit">xUnit</a> tests with MSBuild, so I've spent some time for creating a MSBuild project running the tests. Luckily xUnit already have <a href="http://xunit.codeplex.com/wikipage?title=HowToUseMSBuild&referringTitle=HowToUseTeamCity">MSBuild tasks</a> so I just needed to hook it up.<br />
I wanted to search for all xUnit unit test dlls inside a folder and run the tests there. So I'm searching for <b>xunit.core.dll </b>file to get all the folders eventually containing such dlls and then search all these folders for a pattern - <b>*.Tests.dll</b>. When the tests dlls are found <b>xunit</b> task is run for all of them. So here's the result <b>.proj</b> file:<br />
<pre class="brush: xml"><?xml version="1.0" encoding="utf-8"?>
<Project
DefaultTargets="Test"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask
AssemblyFile="xunit.runner.msbuild.dll"
TaskName="Xunit.Runner.MSBuild.xunit"/>
<UsingTask TaskName="GetAssemblies" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<XUnitFileName ParameterType="System.String" Required="false"/>
<TestsSearchPattern ParameterType="System.String" Required="false"/>
<Assemblies ParameterType="System.String[]" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
var xUnitFileName = XUnitFileName ?? "xunit.core.dll";
var testsSearchPattern = TestsSearchPattern ?? "*.Tests.dll";
// get all the directories containing xUnit dlls
var directories = System.IO.Directory.GetFiles(Path, xUnitFileName, System.IO.SearchOption.AllDirectories)
.Select(file => System.IO.Path.GetDirectoryName(file));
var assembliesList = new List<string>();
foreach(string directory in directories)
{
// get all test dlls from the given paths
assembliesList.AddRange(System.IO.Directory.GetFiles(directory, testsSearchPattern, System.IO.SearchOption.TopDirectoryOnly));
}
Assemblies = assembliesList.ToArray();
]]>
</Code>
</Task>
</UsingTask>
<Target Name="Test">
<GetAssemblies Path="$(BuildRoot).">
<Output PropertyName="TestAssemblies" TaskParameter="Assemblies"/>
</GetAssemblies>
<xunit Assemblies="$(TestAssemblies)" />
</Target>
</Project>
</pre>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com1tag:blogger.com,1999:blog-925946298101307708.post-61435304819392502952015-05-15T00:54:00.000+03:002015-05-15T00:56:37.493+03:00Git History tool<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
This is a small tool getting history for a path in a given local git repository and exporting it using <a href="https://github.com/volkovku/RazorTemplates">RazorTemplates</a><br />
<br />
The tool get all the changes between revisions or to a revision of a local git repository and pass the commits to a Razor template which then outputs the result in a given file. The commits can be filtered by a given pat in the repository.
<br />
<br />
<b>Usage</b>: <i>GitHistory.App.exe</i> <i>[-gitRepo] [-templateFile] [-outputFile] [-endRevision ] [-gitInstallationFolder ] [-includeMerges] [-pageTitle ] [-pathInRepo ] [-startRevision ] </i><br />
<br />
To use the tool you can pass the following arguments:<br />
<br />
<ul style="text-align: left;">
<li><b>gitRepo</b>(or just <b>r</b>) - The local path to the git repository </li>
<li><b>templateFile</b>(or just <b>t</b>) - The path to the Razor template file that will be used for tansforming the list of commits </li>
<li><b>outputFile</b>(or just <b>o</b>) - The path to the file where the result content will be saved </li>
<li><b>startRevision</b>(or <b>r</b> or <b>revision</b>) - This argument is optional and is needed if you want to get the changes in a range or to a revision </li>
<li><b>endRevision</b>(or just <b>er</b>) - Optional argument that specifies the end revision </li>
<li><b>includeMerges</b>(or just <b>im</b>) - Whether or not the merge commits to be included in the output </li>
<li><b>gitInstallationFolder</b>(or just gi) - The path to the git executable if it is not set in the Path environment variable </li>
<li><b>pathInRepo</b>(or just <b>p</b>) - The relative path inside the local repository where you want to get the changes for </li>
<li><b>pageTitle</b>(or just <b>pt</b>) - This can be used for presentation purposes only. Can be passed to the command line and then output in the result </li>
</ul>
<div>
You can find the tool on github <a href="https://github.com/vtrifonov/GitHistory">here</a>. Example usage(<b>example.bat</b>) and razor(<b>example.razor</b>) files inside the project folder.</div>
</div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-88614911535739439452015-04-24T12:26:00.000+03:002015-04-24T12:32:07.757+03:00ConfigurationTransformations Nuget<div dir="ltr" style="text-align: left;" trbidi="on">
As in my recent work I had to deal with some <a href="https://msdn.microsoft.com/en-us/library/vstudio/dd465326(v=vs.100).aspx">configuration transformations</a> from C# code I've created a nuget that can be used if you want to apply a transformation programmatically. To do that you can install the <a href="https://www.nuget.org/packages/ConfigurationTransformations">ConfigurationTransformation</a> package from the <a href="https://www.nuget.org/">official nuget repository</a>.<br />
In order to use it you can call the following from the package manager console:<br />
<pre class="brush: powershell">PM> Install-Package ConfigurationTransformations
</pre>
<br />
and then just create an instance of the ConfigTransformer class to apply the transformation.
<br />
<pre class="brush: csharp">ConfigTransformer configTransformer = new ConfigTransformer();
string sourceFile = @"C:\temp\web.config";
string transformationFile = @"C:\temp\web.Release.config";
string destinationFile = @"C:\temp\resultweb.config";
TransformationResult transformationResult = configTransformer.ApplyTransformation(sourceFile, transformationFile, destinationFile);
</pre>
If you don't specify the destination file the source file content will be changed with the result transformated content.<br />
<br />
The other option is to get the result content without saving it to a file:
<br />
<pre class="brush: csharp">ConfigTransformer configTransformer = new ConfigTransformer();
string sourceFile = @"C:\temp\web.config";
string transformationFile = @"C:\temp\web.Release.config";
string transformedContent = configTransformer.GetTransformedFileContent(sourceFile, transformationFile);
</pre>
<br />
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com1tag:blogger.com,1999:blog-925946298101307708.post-43069507376487984582015-04-21T15:26:00.000+03:002015-04-22T11:02:06.930+03:00Small helper tool for configuration transformations<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
In my daily work I often use <a href="https://msdn.microsoft.com/en-us/library/vstudio/dd465326(v=vs.100).aspx">configuration transformations</a> for different settings for the different environment and often I need to add a new transformation for a new environment. So I've decided to create a small tool that can automate adding new transformations to all the projects in a solution. In addition to this I've added functionality for applying config transformations as this can be useful if you want to test the transformations from command line. Here's a short description of the tool:<br />
<br />
<b>TransformHelper</b><br />
<br />
Small tool helping with the usage of the <a href="https://msdn.microsoft.com/en-us/library/vstudio/dd465326(v=vs.100).aspx">configuration transformations</a><br />
<br />
It has two<br />
<br />
<ul style="text-align: left;">
<li><b>Add</b> - passing solution, existing transformation and the new transformation the tool will go through all the projects in the solution and check for configuration files having the existing transformation and will add new transformation copying the files from the existing </li>
<li style="box-sizing: border-box;"><strong style="box-sizing: border-box;">Remove</strong> - passing solution, and existing transformation the tool will go through all the projects in the solution and check for configuration files having the existing transformation and will delete them</li>
<li><b>Apply</b> - passing source file, transformation file and optional target file (if not passed the source file content will be replaced)</li>
<li style="box-sizing: border-box;"><strong style="box-sizing: border-box;">ApplySLN</strong> - passing solution, and existing transformation the tool will go through all the projects in the solution and apply the transformation on the original file</li>
</ul>
<div>
<b>Usage</b></div>
<div>
<ul style="text-align: left;">
<li><b>Add </b>mode (you may not specify the mode here as the Add mode is the default behavior)</li>
</ul>
</div>
<pre class="brush: ps">TransformHelper.exe [-mode add] -solution C:\Work\MySolution.sln -existing Dev -new Live
</pre>
<br />
This will check all the projects in the solution for files like <b>[fileName].Dev.config</b> and will add the corresponding <b>[fileName].Live.config</b> files copying the content from the existing ones
<br />
<div>
<ul>
<li><b>Remove </b>mode</li>
</ul>
</div>
<pre class="brush: ps">TransformHelper.exe -mode remove -solution C:\Work\MySolution.sln -existing Dev
</pre>
<br />
This will check all the projects in the solution for files like <b>[fileName].Dev.config</b> and will delete them and remove them from the project<br />
<ul style="text-align: left;">
<li><b>Apply </b>mode</li>
</ul>
<pre class="brush: ps">TransformHelper.exe -mode apply -source "C:\Work\MySolution\WebProject\web.config" -transformFile "C:\Work\MySolution\WebProject\web.Dev.config" -target "C:\Temp\result.web.config"
</pre>
<br />
This will apply the transformation file <b>C:\Work\MySolution\WebProject\web.Dev.config</b> on <b>C:\Work\MySolution\WebProject\web.config</b> and the result content will be saved in <b>C:\Temp\result.web.config</b> file
<br />
<div>
<ul>
<li><b>ApplySLN </b>mode</li>
</ul>
</div>
<pre class="brush: ps">TransformHelper.exe -mode applySLN -solution C:\Work\MySolution.sln -existing Dev
</pre>
<br />
This will check all the projects in the solution for files like <b>[fileName].Dev.config</b> and if there are files like <b>[fileName].config</b> as well in the project they will be transformed using the Dev transformations<br />
<br />
You can find the tool on github <a href="https://github.com/vtrifonov/TransformHelper">here</a>. </div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-18200213923538106712015-01-14T11:32:00.002+02:002015-01-14T12:03:39.146+02:00Online GUID converter<div dir="ltr" style="text-align: left;" trbidi="on">
<style type="text/css">
.textContent
{
width: 100%;
}
.encodeButton
{
background: none repeat scroll 0 0 #3D9400;
border: 1px solid #29691D;
color: #FFFFFF;
text-shadow: 0 1px rgba(0, 0, 0, 0.1);
border-radius: 2px 2px 2px 2px;
font-size: 11px;
font-weight: bold;
height: 27px;
line-height: 27px;
padding: 0 8px;
text-align: center;
cursor: pointer
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
Sometimes I need to convert GUIDs without dashes to GUIDs with dashes or vice versa, so I've created this small page to help me with the conversion.
<br />
<br />
<div dir="ltr" style="text-align: left;" trbidi="on">
<p><input class="textContent" id="taInput" type="text"/></p>
<p><input class="encodeButton" id="bntEncode" type="submit" value="Convert" /> or <input class="encodeButton" id="bntGenerateNew" type="button" value="Generate new" /></p>
<p><input class="textContent" id="taOutput" type="text"/></p>
</div>
<script type="text/javascript">
$(document).ready(function () {
$("#bntEncode").on("click", function () {
var taInput = $("#taInput");
var taOutput = $("#taOutput");
taOutput.val(convert(taInput.val()));
});
$("#bntGenerateNew").on("click", function () {
var taInput = $("#taInput");
var taOutput = $("#taOutput");
taInput.val(generateGuid());
taOutput.val(convert(taInput.val()));
});
});
function convert(guid){
if (guid.length == 32){
return addDashes(guid);
}
else if(guid.length == 36){
return removeDashes(guid);
}
else{
return "Invalid GUID - " + guid;
}
}
function addDashes(guidNoDashes) {
guidNoDashes = guidNoDashes.trim();
if (guidNoDashes.indexOf('-') > -1) {
return guidNoDashes;
} else {
var result = guidNoDashes.substring(0, 8) + "-" + guidNoDashes.substring(8, 12) + "-" + guidNoDashes.substring(12, 16) + "-" + guidNoDashes.substring(16, 20) + "-" + guidNoDashes.substring(20, 32);
return result;
}
}
function removeDashes(guidWithDashes) {
guidWithDashes = guidWithDashes.trim();
var result = guidWithDashes.replace(/-/g, '');
return result;
}
function generateGuid(){
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
</script>
</div>Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-59318474262985599332013-09-17T16:02:00.000+03:002013-09-17T16:09:22.483+03:00How to build a specific projects from a solution with MSBuild<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Sometimes you want to build only a single project, or a list of projects from a solution without building the whole solution with MSBuild.<br />
<br />
To do so, you can use custom build targets. You need to add a custom build target for every project you want to build containing the relative path in solution structure of the project (.csproj/.vproj).<br />
Another thing you need to have in mind is that the dots(.) in the project name should be replaced with underscores(_). You can clean or rebuild adding :Clean, :Rebuild at the end of the target. <br />
<br />
However if you can check all the MSBuild targets used from the solution. As you may know, when running MSBuild command against a solution file in-memory it is converted to an actual MSBuild project named [SolutionName].sln.metaproj. You can add an environment variable named <strong><em>msbuildemitsolution </em></strong>and set its value to 1 and run MSBuild from command line. Another option is to add <strong><em>/p:msbuildemitsolution=1</em></strong> to the passed parameters to the build. This will generate the .metaproj file where you can find all the needed targets.<br />
<br />
Lets illustrate all the above with a simple example.<br />
Here is a simple project structure:<br />
<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhi1trL5RvgaFNnBBUsDUz_xCj-K8yOP1RIA1j_ix4ERyarO2-deO10APMLSs6gyUwftgId79rVC6K4vBz2losSDAzahr1qVW092eBzUw-2um6WJXDmjHYHWqryXHcQNt-slxKndOc59k8/s1600/BuildSpecificProjectsFromASolutionScreenShot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhi1trL5RvgaFNnBBUsDUz_xCj-K8yOP1RIA1j_ix4ERyarO2-deO10APMLSs6gyUwftgId79rVC6K4vBz2losSDAzahr1qVW092eBzUw-2um6WJXDmjHYHWqryXHcQNt-slxKndOc59k8/s320/BuildSpecificProjectsFromASolutionScreenShot.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<div align="left" class="separator" style="clear: both; text-align: center;">
</div>
<div align="left" class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
If we want to build Project.Four and rebuild ProjectTwo we need to call MSBuild with the following arguments:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<div class="separator" style="clear: both; text-align: left;">
<em>msbuild BuildSpecificProjectsFromASolution.sln /t:"Folder2\Project_Four";"ProjectTwo:Rebuild"</em></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
You can find the sample solution <a href="https://sites.google.com/site/vtrifonov/how-to-build-specific-projects-from-a-solution-with-msbuild/BuildSpecificProjectsFromASolution.zip?attredirects=0&d=1">here</a>.</div>
</div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com1Sofia, Bulgaria42.6978388 23.32166970000002941.9504478 22.03077620000003 43.4452298 24.612563200000029tag:blogger.com,1999:blog-925946298101307708.post-26537551875342886422012-12-18T12:12:00.000+02:002013-09-17T18:44:27.463+03:00Using WIX with managed custom action<div dir="ltr" style="text-align: left;" trbidi="on">
<a href="http://wixtoolset.org/">WIX</a> is a great toolset for creating installers. In most of the cases when you need an installer you need some custom logic to be executed. It's great that WIX supports managed custom actions. Anyway it wasn't so easy for me to make it work, so I want to share my experience.<br />
I will demonstrate it using the <a href="http://wixtoolset.org/releases/">WIX Visual Studio add-in (version v3.7.1217.0).</a><br />
<br />
We will create WIX Setup project and C# Custom Action Project.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjADYMoz6hKYWU7tJ3KJERTyKHOuiPQTAk_Z7S45_FY0CEaTPLID6-E5ILoJN1rGQSKPw6WWFFQMpg_w2mSqK8G_KPydy-hqrzOFno1sTnimQf3QZ3o6NnNfFH6nwcW1Y79XK_abFRctLjV/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjADYMoz6hKYWU7tJ3KJERTyKHOuiPQTAk_Z7S45_FY0CEaTPLID6-E5ILoJN1rGQSKPw6WWFFQMpg_w2mSqK8G_KPydy-hqrzOFno1sTnimQf3QZ3o6NnNfFH6nwcW1Y79XK_abFRctLjV/s320/Untitled.png" width="320" /></a></div>
We will add a dummy text file in the setup project to be used as installation content and will change a little bit the auto created Product.wxs file.<br />
<pre class="brush: xml"><?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="WixWithCustomAction" Language="1033" Version="1.0.0.0" Manufacturer="Trifonov" UpgradeCode="60468a7d-6485-4e7e-bf82-503213bc43a8">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<Media Id='1' Cabinet='Dummy.cab' EmbedCab='yes' />
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder'>
<Directory Id='WixWithCustomAction' Name='WixWithCustomAction'>
<Component Id="DummyContent" Guid="ba9028ae-0d3b-4b66-8560-f53330736265">
<!-- Add the dummy file as content. -->
<File Id="DummyFile" KeyPath="yes" Source="Dummy.txt" Vital="yes" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature Id="Complete" Title="WixWithCustomAction" Level="1">
<ComponentRef Id='DummyContent' />
</Feature>
</Product>
</Wix>
</pre>
That's how our solution looks like:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXynMR3MuQE_JHKbmsY9TPED5Uh7pT6D0ooi1SDEMl7bspSJUX8Gvrx6QLX5g8yWaKtczPpQ1GeSdYLDaQux-O6U1jX2m8XRO4HdlGGwIZCUCMBSnrIgvXSvNESZiR3q49CcMy20m4ZeIS/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXynMR3MuQE_JHKbmsY9TPED5Uh7pT6D0ooi1SDEMl7bspSJUX8Gvrx6QLX5g8yWaKtczPpQ1GeSdYLDaQux-O6U1jX2m8XRO4HdlGGwIZCUCMBSnrIgvXSvNESZiR3q49CcMy20m4ZeIS/s320/Untitled.png" width="320" /></a></div>
<br />
If we build the WixWithCustomAction project, <i><b>WixWithCustomAction.msi</b></i> will be created. If we run it <b>WixWithCustomAction</b> folder will be created in program files with Dummy.txt file inside.<br />
But now we want to add a custom action which will create a file in C:\Temp folder. We will use the MyCustomActionProject for this. Let's change the CustomAction class a little bit:<br />
<pre class="brush: csharp">using Microsoft.Deployment.WindowsInstaller;
using System.IO;
namespace MyCustomActionProject
{
public class CustomActions
{
[CustomAction]
public static ActionResult MyCustomAcion(Session session)
{
session.Log("Executing MyCustomAcion");
File.CreateText(@"c:\temp\installed.txt");
return ActionResult.Success;
}
}
}
</pre>
Now we just need to call this custom action from the installer. To do this we will add a reference to this project in the setup project.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho9AzyuYG1E_6fyPVs1NbXXMWavEMmxtaJK295panZWL6rjeEQZadDBud2II6DoZ_U_xHDKb3CDTsTaGF3gV67ntco35bvdbWUjrto9zd9BUPid8UBWlKBcYPdCtgwk74A1x3lAMkWsh0m/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="275" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho9AzyuYG1E_6fyPVs1NbXXMWavEMmxtaJK295panZWL6rjeEQZadDBud2II6DoZ_U_xHDKb3CDTsTaGF3gV67ntco35bvdbWUjrto9zd9BUPid8UBWlKBcYPdCtgwk74A1x3lAMkWsh0m/s320/Untitled.png" width="320" /></a></div>
Now let's add the custom action in the <b>Product.wxs</b> file.<br />
Adding the project as a reference allows as to use <a href="http://wixtoolset.org/documentation/manual/v3/votive/votive_project_references.html">these variables</a>.<br />
But adding custom action is a little bit complicated. After building the MyCustomActionProject.dll file we will need a call to <b>MakeSfxCA.exe</b> and <b>sfxca.dll</b> in your installed WiX toolset as the dll need a reference to Microsoft.Deployment.<b>WindowsInstaller.dll</b> and has <b>CustomAction.config </b>attached. Calling the MakeSfxCA.exe tool will package the project output to <b>MyCustomActionProject.CA.dll</b>(<a href="http://wix.tramontana.co.hu/tutorial/events-and-actions/how-to-manage">here</a> you can find some additional information about this).<br />
As we use "<b>C# Custom Action Project</b>" there is an import added to <b>$(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.CA.targets</b> file which will create this package on build. To check this you can build the custom action project and see the output:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqB077ClswT7B0mho6mkdAVKjIuHy8qlxcJDFnDdqCXl3iFaBwZosJ-4_q52ocdFu9392U8D8iy07XECV6ALKcAjpw4kI7a0vHL9-x4cu__2XglHcJ7vLFconHvs4EoLZtTAbUii0nk_gm/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="52" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqB077ClswT7B0mho6mkdAVKjIuHy8qlxcJDFnDdqCXl3iFaBwZosJ-4_q52ocdFu9392U8D8iy07XECV6ALKcAjpw4kI7a0vHL9-x4cu__2XglHcJ7vLFconHvs4EoLZtTAbUii0nk_gm/s320/Untitled.png" width="320" /></a></div>
So the custom action in the <b>Product.wxs</b> needs to reference the .CA.dll file. Thats why we cannot use <br />
$(var.MyCustomActionProject.TargetPath) as a source for the custom action binary, but we will have to construct the source path like this:<br />
$(var.MyCustomActionProject.TargetDir)$(var.MyCustomActionProject.TargetName).CA.dll<br />
The other option is not to use the project reference but add the full path to the custom action output.<br />
So we will add the following rows to the <b>wxs</b> file<br />
<pre class="brush: xml"><!--The source file should be the MyCustomActionProject.CA.dll file, that's why it's constructed this way-->
<Binary Id='CustomActionBinary' SourceFile='$(var.MyCustomActionProject.TargetDir)$(var.MyCustomActionProject.TargetName).CA.dll' />
<!--The DllEntry must be the name of the method to be called from the custom action project, in our case - MyCustomActionMethod
http://wix.tramontana.co.hu/tutorial/events-and-actions/at-a-later-stage
The Execute attribute will specify the deferred status of our custom action.
And finally, HideTarget will allow us to disable logging the parameteres passed to this custom action if security considerations so dictate.-->
<CustomAction Id='CustomActionId' BinaryKey='CustomActionBinary' DllEntry='MyCustomActionMethod' Execute="deferred" HideTarget="yes"/>
<InstallExecuteSequence>
<!--We want to call the custom action before the install finalizes-->
<Custom Action='CustomActionId' Before='InstallFinalize'/>
</InstallExecuteSequence>
</pre>
And that's it. Now if we build the setup project and run the created msi installer, <b>c:\temp\installed.txt</b> will be created as a part of the installation process.<br />
<br />
You can find the solution file here - <a href="https://sites.google.com/site/vtrifonov/using-wix-with-managed-custom-action/WixWithCustomAction.zip">WixWithCustomAction.zip</a>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com4tag:blogger.com,1999:blog-925946298101307708.post-51633230021992837452012-11-30T12:01:00.000+02:002015-04-21T15:26:32.351+03:00Sending stream to ServiceStack<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Recently I needed to make a <a href="http://www.servicestack.net/">ServiceStack</a> service which can receive
big files, so I wanted to use streaming to accomplish this.
Unfortunately there isn't much information about using streams with
ServiceStack, so I decided to share my experience.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbHvAY1jCTqYrmeZ8kQ5-Vj8-iRXloV7VP8vFvi78oHCKBLDXyuEe5Y_pUtmJC38mvXFdc8pHldOPsMmiW2UBRntA5-6AfMgJMLK7dQDfwtN8iaQzWozh1CXldAy8NR-XV7UVyrul3WQeI/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbHvAY1jCTqYrmeZ8kQ5-Vj8-iRXloV7VP8vFvi78oHCKBLDXyuEe5Y_pUtmJC38mvXFdc8pHldOPsMmiW2UBRntA5-6AfMgJMLK7dQDfwtN8iaQzWozh1CXldAy8NR-XV7UVyrul3WQeI/s320/Untitled.png" height="142" width="320" /></a></div>
<br />
We'll
create a sample solution containing both Server and Client. We will
create a class library containing the service itself and an Utility
project. So here is the structure of our solution:<br />
So let's continue with the implementation of the service.<br />
First of all we'll need to install the ServiceStack nuget package in the ServiceStackStreaming.Service project:<br />
<pre class="brush: ps">PM> Install-Package ServiceStack
</pre>
This will add the dlls needed for ServiceStack. Now let's create the DTO:<br />
<pre class="brush: csharp">using ServiceStack.ServiceHost;
namespace ServiceStackStreaming.Service
{
[Route("/upload/{FileName}", "POST")]
public class UploadPackage : IRequiresRequestStream
{
public System.IO.Stream RequestStream { get; set; }
public string FileName { get; set; }
}
}
</pre>
To enable Streaming support we need to implement <b>IRequiresRequestStream</b> which needs a RequestStream property of type System.IO.Stream. We'll add a FileName property and include it in the Route so that we would be able to pass the uploaded file name.<br />
The next thing to do is to create the service itself:<br />
<pre class="brush: csharp">using ServiceStack.Common.Web;
using ServiceStackStreaming.Utility;
using System;
using System.IO;
namespace ServiceStackStreaming.Service
{
public class UploadService : ServiceStack.ServiceInterface.Service
{
public object Post(UploadPackage request)
{
// hack - get the properties from the request
if (string.IsNullOrEmpty(request.FileName))
{
var segments = base.Request.PathInfo.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
request.FileName = segments[1];
}
string resultFile = Path.Combine(@"C:\Temp", request.FileName);
if (File.Exists(resultFile))
{
File.Delete(resultFile);
}
using (FileStream file = File.Create(resultFile))
{
request.RequestStream.Copy(file);
}
return new HttpResult(System.Net.HttpStatusCode.OK);
}
}
}
</pre>
<br />
Our dummy service will save the incoming file in the "C:\Temp" directory. With the code from line 12 to line 17 we are getting the FileName property if it's not set. It seems that when using streaming the additional properties are not processed and they are always null, so we'll do this little hack to get the properties parsing the request url.<br />
The other trick we use here is the extension method of the System.IO.Stream class wich we have implemented in the <b>ServiceStackStreaming.Utility</b> project:<br />
<pre class="brush: csharp">using System.IO;
namespace ServiceStackStreaming.Utility
{
public static class StreamExtender
{
public static void Copy(this Stream instance, Stream target)
{
int bytesRead = 0;
int bufSize = copyBuf.Length;
while ((bytesRead = instance.Read(copyBuf, 0, bufSize)) > 0)
{
target.Write(copyBuf, 0, bytesRead);
}
}
private static readonly byte[] copyBuf = new byte[0x1000];
}
}
</pre>
this simply copes the instance stream to the target stream. Another option is to use ServiceStack <a href="https://github.com/ServiceStack/ServiceStack.Text/blob/master/src/ServiceStack.Text/StreamExtensions.cs">StreamExtensions</a> WriteTo method instead of creating this utility method.<br />
The last thing we need to do to create a functional service is to add the AppHost class, we will inherit <b>AppHostHttpListenerBase</b> as we want to host the service in a window console application.<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<pre class="brush: csharp">using ServiceStack.WebHost.Endpoints;
namespace ServiceStackStreaming.Service
{
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Agent", typeof(UploadService).Assembly) { }
public override void Configure(Funq.Container container)
{
// we can add the routing here instead of adding it as attribute to the DTO
//Routes
// .Add<uploadpackage>("/upload/{FileName}", "POST");
}
}
}
</uploadpackage></pre>
We can configure the route here, but I prefer doing this with attribute.<br />
Now let's host the service. To do this we'll need to add the same ServiceStack nuget to the <b>SertviceStackStreaming.Server</b> project and add the following code to the Program.cs file:<br />
<pre class="brush: csharp">using ServiceStackStreaming.Service;
using System;
namespace ServiceStackStreaming.Server
{
class Program
{
static void Main(string[] args)
{
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:1999/");
Console.WriteLine("Service listening on port 1999!");
Console.ReadKey();
}
}
}
</pre>
<br />
This will be enough to host the service listening to port 1999.<br />
Now let's call the service from the <b>ServiceStackStreaming.Client</b> (again we'll have to instal the sam e nuget package here).<br />
<pre class="brush: csharp">using ServiceStackStreaming.Utility;
using System.IO;
using System.Net;
namespace ServiceStackStreaming.Client
{
class Program
{
static void Main(string[] args)
{
string filePath = @"c:\temp\upload.zip";
HttpWebRequest client = (HttpWebRequest)WebRequest.Create("http://localhost:1999/upload/upload-copy.zip");
client.Method = WebRequestMethods.Http.Post;
// the following 4 rows enable streaming
client.AllowWriteStreamBuffering = false;
client.SendChunked = true;
client.ContentType = "multipart/form-data;";
client.Timeout = int.MaxValue;
using (FileStream fileStream = File.OpenRead(filePath))
{
fileStream.Copy(client.GetRequestStream());
}
var response = new StreamReader(client.GetResponse().GetResponseStream()).ReadToEnd();
}
}
}
</pre>
And that's it. We create WebRequest, set the needed properties to enable streaming on the client and copy the file stream to the request stream. This will call the service and will upload the "C:\Temp\upload.zip" file as upload-copy.zip file.<br />
<br />
You can find the sample code here: <a href="https://sites.google.com/site/vtrifonov/sending-stream-to-servicestack/ServiceStackStreaming.zip">ServiceStackStreaming.zip</a> </div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com1tag:blogger.com,1999:blog-925946298101307708.post-14643108935610547132012-11-15T19:32:00.001+02:002013-09-17T18:43:24.843+03:00Testing private methods with Visual Studio<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
When writing unit tests in most of the cases we have to test private methods. Let's see how this can be achieved using Visual Studio. I will give you three options: <br />
<br />
<ol>
<li>
You can test private methods using <span style="color: #444444;"><span style="background-color: yellow;">Reflection</span></span>, but this is not always a good option, as when you change the name or input arguments of a Method, you won't get any error when building, but you will get exceptions run time.<br />
We will use the project from my previous post <a href="http://www.vtrifonov.com/2012/11/getting-console-output-within-unit-test.html">Getting console output within a unit test</a>.<br />
We will add a private method to the DummyClass:
<br />
<pre class="brush: csharp">using System;
namespace ConsoleLogger
{
public class DummyClass
{
public void WriteToConsole(string text)
{
Console.Write(text);
}
private void PrivateWriteToConsole(string text)
{
Console.Write("Private: " + text);
}
}
}
</pre>
Now we can add a unit test to test the method using reflection:<br />
<pre class="brush: csharp">[TestMethod]
public void PrivateWriteToConsoleReflection()
{
var currentConsoleOut = Console.Out;
DummyClass target = new DummyClass();
Type type = typeof(DummyClass);
string text = "Hello";
using (var consoleOutput = new ConsoleOutput())
{
var method = type.GetMethod("PrivateWriteToConsole",
BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(target, new object[1] { text });
Assert.AreEqual(string.Format("Private: {0}", text),
consoleOutput.GetOuput());
}
Assert.AreEqual(currentConsoleOut, Console.Out);
}</pre>
We get and invoke the method using reflection.<br />
<br />
</li>
<li>Another options is to use <span style="color: #444444;"><span style="background-color: yellow;">PrivateObject</span></span> class. Using it you can easily call private method, but you have the same problem, you won't get compile exception when name or parameters are changed. Here is the same test written using PrivateObject:
<pre class="brush: csharp">[TestMethod]
public void PrivateWriteToConsolePrivateObject()
{
var currentConsoleOut = Console.Out;
PrivateObject target = new PrivateObject(typeof(DummyClass));
string text = "Hello";
using (var consoleOutput = new ConsoleOutput())
{
target.Invoke("PrivateWriteToConsole", text);
Assert.AreEqual(string.Format("Private: {0}", text),
consoleOutput.GetOuput());
}
Assert.AreEqual(currentConsoleOut, Console.Out);
}
</pre>
</li>
<li>And here is the third option which I think is the best. You can add <span style="color: #444444;"><span style="background-color: yellow;">.accessor</span></span> file containing the name of the assembly whose private methods you want to see, or you can use Visual Studio "Create Unit Tests..." wizard to do this for you:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1HoqziYCB5mEaNrm6FJ3W4BsAK8-5JD3G-s3J7M-Ms41mPz_z3Jqg7eSzhFvwQvN4Q6esOq5JtHt8Xx5XSfYRh5_KWRyS1weW1Ff8FVTeDzxbFVXm_mpv-tlbxA8o0iABBG4DAcPyt3Pm/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1HoqziYCB5mEaNrm6FJ3W4BsAK8-5JD3G-s3J7M-Ms41mPz_z3Jqg7eSzhFvwQvN4Q6esOq5JtHt8Xx5XSfYRh5_KWRyS1weW1Ff8FVTeDzxbFVXm_mpv-tlbxA8o0iABBG4DAcPyt3Pm/s320/Untitled.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJtCy2dmstJk4RS1q_fVZtjdx1dcPVHWPpKr8bZ_wJNKquuj_cuvzyCXxDo-YHz2dUWK0965rWTMAxrFN_8i3HMsy51R_8PKSsBxHXgY_VLxiRIDQccsht5BJSw3cfdBRDxHOOUPStmfaD/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJtCy2dmstJk4RS1q_fVZtjdx1dcPVHWPpKr8bZ_wJNKquuj_cuvzyCXxDo-YHz2dUWK0965rWTMAxrFN_8i3HMsy51R_8PKSsBxHXgY_VLxiRIDQccsht5BJSw3cfdBRDxHOOUPStmfaD/s320/Untitled.png" width="320" /></a></div>
this will add the following unit test:
<pre class="brush: csharp">/// <summary>
///A test for PrivateWriteToConsole
///</summary>
[TestMethod]
[DeploymentItem("ConsoleLogger.exe")]
public void PrivateWriteToConsoleTest()
{
DummyClass_Accessor target = new DummyClass_Accessor(); // TODO: Initialize to an appropriate value
string text = string.Empty; // TODO: Initialize to an appropriate value
target.PrivateWriteToConsole(text);
Assert.Inconclusive("A method that does not return a value cannot be verified.");
}
</pre>
we will modify it for our needs:
<pre class="brush: csharp">/// <summary>
///A test for PrivateWriteToConsole
///</summary>
[TestMethod]
[DeploymentItem("ConsoleLogger.exe")]
public void PrivateWriteToConsoleTest()
{
var currentConsoleOut = Console.Out;
DummyClass_Accessor target = new DummyClass_Accessor();
string text = "Hello";
using (var consoleOutput = new ConsoleOutput())
{
target.PrivateWriteToConsole(text);
Assert.AreEqual(string.Format("Private: {0}", text),
consoleOutput.GetOuput());
}
Assert.AreEqual(currentConsoleOut, Console.Out);
}
</pre>
When Visual Studio builds the project it will generate ConsoleLogger_Accessor.exe assembly, containing DummyClass_Accessor class with public methods only. </li>
<li>
Just to mention that you can test internal methods using the same approaches, but you will have one more option - in the AssemblyInfo file of the assembly being tested, you can add <span style="color: #444444;"><span style="background-color: yellow;">InternalsVisibleTo</span></span> attribute to specify which assembly will see the internal methods, in our case:
<pre class="brush: csharp">[assembly: InternalsVisibleTo("ConsoleLogger.Tests")]
</pre>
now we will add an internal method to the same class:
<pre class="brush: csharp">using System;
namespace ConsoleLogger
{
public class DummyClass
{
public void WriteToConsole(string text)
{
Console.Write(text);
}
private void PrivateWriteToConsole(string text)
{
Console.Write("Private: " + text);
}
internal void InternalWriteToConsole(string text)
{
Console.Write("Internal: " + text);
}
}
}
</pre>
and here is the working test method:
<pre class="brush: csharp">[TestMethod]
public void InternalWriteToConsoleTest()
{
var currentConsoleOut = Console.Out;
DummyClass target = new DummyClass();
string text = "Hello";
using (var consoleOutput = new ConsoleOutput())
{
target.InternalWriteToConsole(text);
Assert.AreEqual(string.Format("Internal: {0}", text),
consoleOutput.GetOuput());
}
Assert.AreEqual(currentConsoleOut, Console.Out);
}
</pre>
</li>
</ol>
And here's the code - <a href="https://sites.google.com/site/vtrifonov/testing-private-methods-with-visual-studio/ConsoleLogger.zip">ConsoleLogger.zip</a></div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-64889060135692810382012-11-15T13:50:00.003+02:002012-11-30T16:33:16.680+02:00Getting console output within a unit test<div dir="ltr" style="text-align: left;" trbidi="on">
Today I needed to test a method which writes to the Console to validate the ouput. It is not hard to change the default console output and check the result. However you may forget to return the original output at the end. So let's take a look at my solution.<br />
<br />
Let say we have the following class we want to test:<br />
<pre class="brush:csharp">using System;
namespace ConsoleLogger
{
public class DummyClass
{
public void WriteToConsole(string text)
{
Console.Write(text);
}
}
}
</pre>
I have created a small helper class to redirect the output to a StringWriter:<br />
<pre class="brush:csharp">using System;
using System.IO;
namespace ConsoleLogger.Tests
{
public class ConsoleOutput : IDisposable
{
private StringWriter stringWriter;
private TextWriter originalOutput;
public ConsoleOutput()
{
stringWriter = new StringWriter();
originalOutput = Console.Out;
Console.SetOut(stringWriter);
}
public string GetOuput()
{
return stringWriter.ToString();
}
public void Dispose()
{
Console.SetOut(originalOutput);
stringWriter.Dispose();
}
}
}
</pre>
Now let's write the unit test:<br />
<pre class="brush:csharp">using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ConsoleLogger.Tests
{
[TestClass]
public class DummyClassTest
{
[TestMethod]
public void WriteToConsoleTest()
{
var currentConsoleOut = Console.Out;
DummyClass target = new DummyClass();
string text = "Hello";
using (var consoleOutput = new ConsoleOutput())
{
target.WriteToConsole(text);
Assert.AreEqual(text, consoleOutput.GetOuput());
}
Assert.AreEqual(currentConsoleOut, Console.Out);
}
}
}
</pre>
This way we are sure that the original output will be restored and it's easy to get the output from the console.<br />
<br />
You can find the sample here <a href="https://sites.google.com/site/vtrifonov/getting-console-output-within-a-unit-test/ConsoleLogger.zip">ConsoleLogger.zip</a>.</div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none">CodeProject</a>Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-17944618990505006022012-11-08T18:40:00.003+02:002013-09-17T19:07:42.934+03:00How to configure local Nuget Repository<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
After my last <a href="http://www.vtrifonov.com/2012/11/share-nuget-packages-between-solutions.html">posts</a> about Nuget packaging I wanted to share another useful experience with Nuget.<br />
You can create a local repository to store all the packages you need and not to download those every time.<br />
<ol>
<li>To do this I have created a folder <b>C:\NugetConfig\Repo</b> and I have copied there the <b>Newtonsoft.Json.4.5.10.nupkg</b> package file</li>
<li>To make the both solutions use this local repository all I have to do is to change the following settings in the NuGet.targets file:
<pre class="brush: xml"><ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default will used the registered sources under %APPDATA%\NuGet\NuGet.Config -->
<!--
<PackageSource Include="https://nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
</ItemGroup>
</pre>
and adding s new PackageSource location
<pre class="brush: xml"><ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default will used the registered sources under %APPDATA%\NuGet\NuGet.Config -->
<!--
<PackageSource Include="https://nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
<PackageSource Include="C:\NugetConfig\Repo" />
</ItemGroup>
</pre>
And that's it. This will make the solution search for the used packages in the given folder and you will get а meaningful error if the package could not be found.
</li>
<li>Furthermore you can add you local repository to the visual studio package sources
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPeVlGWFJzUL0oUfAbGnS4G00NKfG-WP8pedWn6aDJQhR-zXUk240rg-zQxrhV-qpVoCc7995GbbRID0dHeRpSnvTcIq36HBfSxg0OuBSZ1piXTrrliELWf-L631MjsePQSlGlq6cXTPis/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPeVlGWFJzUL0oUfAbGnS4G00NKfG-WP8pedWn6aDJQhR-zXUk240rg-zQxrhV-qpVoCc7995GbbRID0dHeRpSnvTcIq36HBfSxg0OuBSZ1piXTrrliELWf-L631MjsePQSlGlq6cXTPis/s400/Untitled.png" width="400" /></a></div>
so that you will be able to search and add packages from it to any new solution:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzaqU_TBLXVxGwwg800lOFdQ6JTUwsm-R-oHJL_5gVwp-a7906j6p6oE4PWNCERXSzt1SaPPvNqXTxezqiac3bZqo_3mNlHVDK1wH-rlnm9K211Ue6pUpsF-wXlaqEpCFRyCFQBU4vlpqY/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzaqU_TBLXVxGwwg800lOFdQ6JTUwsm-R-oHJL_5gVwp-a7906j6p6oE4PWNCERXSzt1SaPPvNqXTxezqiac3bZqo_3mNlHVDK1wH-rlnm9K211Ue6pUpsF-wXlaqEpCFRyCFQBU4vlpqY/s400/Untitled.png" width="400" /></a></div>
</li>
</ol>
As usual you can find the code here <a href="https://sites.google.com/site/vtrifonov/how-to-configure-local-nuget-repository/NugetConfig-Local-Repo.zip">NugetConfig-Local-Repo.zip</a>.</div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-29184867555567654952012-11-08T17:38:00.000+02:002013-09-17T19:06:45.289+03:00Share Nuget packages between solutions part 2<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Let's see how we can fix the problem explained <a href="http://vasil-trifonov.blogspot.com/2012/11/share-nuget-packages-between-solutions.html">here</a>.<br />
According to the latest <a href="http://docs.nuget.org/docs/release-notes/nuget-2.1">release notes</a> by now you should be able to change the packages folder by adding this setting in the Nuget.config file:<br />
<pre class="brush: xml"><configuration>
<config>
<add key="repositoryPath" value="C:\myteam\teampackages"></add></pre>
<pre class="brush: xml"> </config>
...
</configuration>
</pre>
However unfortunately I couldn't make this work :( So I found a workaround.
I want to make the <b>Second.sln</b> use the package folder of <b>First.sln</b>. So I have made the following changes to the <b>NuGet.targets</b> file.<br />
<ol style="text-align: left;">
<li>I this section:
<pre class="brush: xml"><PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
<!-- Windows specific commands -->
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
</PropertyGroup>
</pre>
<b>I have added the following row:
</b><pre class="brush: xml"><PackagesDir>$(SolutionDir)..\First\packages</PackagesDir> </pre>
</li>
<li>
Change the arguments of the NewGetCommand from:
<pre class="brush: xml"><RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir)</RestoreCommand>
</pre>
to:
<pre class="brush: xml"><RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" -o "$(PackagesDir)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir)</RestoreCommand>
</pre>
<b>I have added <span style="background-color: yellow;"><i>-o "$(PackagesDir)"</i></span> which<i> </i>should make the packages to be downloaded to the</b> <b><i>PackagesDir</i> folder</b></li>
<li>To make the configuration more flexible we can change the <i>PackagesDir</i> definition to
<pre class="brush: xml"><PackagesDir Condition="'$(PackagesDir)' == ''">$(SolutionDir)..\First\packages</PackagesDir> </pre>
this will allow us to predefine the <i>PackagesDir</i> value in the <b>.csproj.user</b> files.</li>
<li>To test this we can add the <b>C:\ NugetConfig\First\Dummy\Dummy.csproj.user</b> with the following content:
<pre class="brush: xml"><?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PackagesDir>C:\NugetConfig\packages</PackagesDir>
</PropertyGroup>
</Project>
</pre>
This will make the Nuget to get files in the <b>C:\NugetConfig\packages </b>folder.</li>
</ol>
And here is the code <a href="https://sites.google.com/site/vtrifonov/share-nuget-packages-between-solutions/NugetConfig-part-2.zip">NugetConfig-part-2.zip</a></div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com4tag:blogger.com,1999:blog-925946298101307708.post-38249962202401129142012-11-08T13:18:00.000+02:002013-09-17T19:06:36.144+03:00Share Nuget packages between solutions part 1<div dir="ltr" style="text-align: left;" trbidi="on">
Many of you may use <a href="http://nuget.org/">Nuget</a> to add reference assemblies to your projects. Anyway sometimes we need to share a project between solutions. In this case if the relative path to this project from the different solutions is different you won't be able to use the default nuget packaging functionality.<br />
Let me explain this with samples.<br />
<ol>
<li>
Let's create an empty Console application named First in folder <b>C:\NugetConfig\First</b></li>
<li>Add a dummy class library project to this solution in the same folder.</li>
<li>Add reference in the console application to the class library.
You should have the following structure now:
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG-oUx58jC9zGw8misq03JT_Mz03zYfpxxnF73ZTPo-sekHFbHJlcKoYKn0nHHyxX26LGqcOgWDSBkuYWT3g8Kw-bJbXSHrMeL3fG8KiBsk6dDrtSe1Z3Lk_ZVxj9djJjysFkCLuHU-VyD/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG-oUx58jC9zGw8misq03JT_Mz03zYfpxxnF73ZTPo-sekHFbHJlcKoYKn0nHHyxX26LGqcOgWDSBkuYWT3g8Kw-bJbXSHrMeL3fG8KiBsk6dDrtSe1Z3Lk_ZVxj9djJjysFkCLuHU-VyD/s400/Untitled.png" width="353" /></a></div>
</li>
<li>Now add Newtonsoft.Json package to the solution:</li>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitqmTaiiJIpcm2QpNwX_zJkbur4UzMz6eAs9hoqBMwlnRZ8ZlOm07iQiCpCWJu8F6tcJtWiS6TiU07WuhrFaxBCHls3Z82Jc-qGKlmvC7-G5ebJsod_3qI-K80ECwUGGyttFsLIqbE_eL6/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitqmTaiiJIpcm2QpNwX_zJkbur4UzMz6eAs9hoqBMwlnRZ8ZlOm07iQiCpCWJu8F6tcJtWiS6TiU07WuhrFaxBCHls3Z82Jc-qGKlmvC7-G5ebJsod_3qI-K80ECwUGGyttFsLIqbE_eL6/s400/Untitled.png" width="400" /></a></div>
<li>Install the package to the Dummy project:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWRptXezHzSvd0rLUMzTOn4llSk7p1HTB-ujHSZfTjI0FqS5uNysNn1ZseAghrPBeCYevQZIvWiUHwpO9s1vZVrzaREIp_1NmZQMD_LGdN46qOGXUsLr39E_gA7y2RbmoaTENAWQbvRR90/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWRptXezHzSvd0rLUMzTOn4llSk7p1HTB-ujHSZfTjI0FqS5uNysNn1ZseAghrPBeCYevQZIvWiUHwpO9s1vZVrzaREIp_1NmZQMD_LGdN46qOGXUsLr39E_gA7y2RbmoaTENAWQbvRR90/s320/Untitled.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2iZ1r6QUsKCwvvqH-v6qofOL0BqDglufLBBJ1XqfP7RMHpVF_Tu6gHl-Pewlpvzc0LXK6pl5kpNl301YeioCzqaq88uNi8BhDExN9i8_botb3nO5W2D7cPg6dhnEP67lwlA34lHQlJDIj/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"></a></div>
</li>
<li>Add a dummy class to the dummy project which references Newton Json:
<pre class="brush: csharp">namespace Dummy
{
public class Dummy
{
public Newtonsoft.Json.Required Required { get; set; }
public Dummy()
{
this.Required = Newtonsoft.Json.Required.Always;
}
public override string ToString()
{
return this.Required.ToString();
}
}
} </pre>
</li>
<li>Add a call to the dummy project in Program.cs
<pre class="brush: csharp">using System;
namespace First
{
class Program
{
public static void Main(string[] args)
{
var dummy = new Dummy.Dummy();
Console.WriteLine(dummy);
}
}
}
</pre>
</li>
<li>Now enable Nuget Package Restore on this solution:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWRptXezHzSvd0rLUMzTOn4llSk7p1HTB-ujHSZfTjI0FqS5uNysNn1ZseAghrPBeCYevQZIvWiUHwpO9s1vZVrzaREIp_1NmZQMD_LGdN46qOGXUsLr39E_gA7y2RbmoaTENAWQbvRR90/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2iZ1r6QUsKCwvvqH-v6qofOL0BqDglufLBBJ1XqfP7RMHpVF_Tu6gHl-Pewlpvzc0LXK6pl5kpNl301YeioCzqaq88uNi8BhDExN9i8_botb3nO5W2D7cPg6dhnEP67lwlA34lHQlJDIj/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2iZ1r6QUsKCwvvqH-v6qofOL0BqDglufLBBJ1XqfP7RMHpVF_Tu6gHl-Pewlpvzc0LXK6pl5kpNl301YeioCzqaq88uNi8BhDExN9i8_botb3nO5W2D7cPg6dhnEP67lwlA34lHQlJDIj/s320/Untitled.png" width="249" /></a></div>
Now if we delete the Newtonsoft.Json folder from the packages folder and run the build the package is downloaded and the solution builds successfully</li>
<li>
Let's create a new project in another solution named Second in folder in folder <b>C:\NugetConfig\Second</b></li>
<li>Add reference to the Dummy project </li>
<li>Add a call to the dummy project in Program.cs
<pre class="brush: csharp">using System;
namespace Second
{
class Program
{
public static void Main(string[] args)
{
var dummy = new Dummy.Dummy();
Console.WriteLine(dummy);
}
}
}
</pre>
</li>
<li>We can build the second solution now as we have already downloaded the needed nuget package. But let's delete the package from <b>C:\NugetConfig\First\packages</b> folder. </li>
<li>Now we won't be able to build the solution. Let's enable Nuget Package Restore on this solution as well.</li>
<li>Trying to build the solution leads to this error:
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaUPvemmDc3iW1OwoOszVG4uJKPdPyMNEPfj2K8dKh_CN3Bk6Lqm61KnBdnyCHMf3G15dSCah3UycDMjrrXddk1GFzf8mGSYy_SoxjcR-w_E6wN5ZFae3rV0JU2Re5c0epxEzbGbCiDAxo/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="67" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaUPvemmDc3iW1OwoOszVG4uJKPdPyMNEPfj2K8dKh_CN3Bk6Lqm61KnBdnyCHMf3G15dSCah3UycDMjrrXddk1GFzf8mGSYy_SoxjcR-w_E6wN5ZFae3rV0JU2Re5c0epxEzbGbCiDAxo/s400/Untitled.png" width="400" /></a></div>
</li>
</ol>
<span style="color: #cc0000;"><span style="font-size: large;">So here is the actual problem</span></span>:<br />
In the Dummy project there is a reference to the NewtonJson
<br />
<pre class="brush: xml"> <Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.4.5.10\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
</pre>
But our folder looks like this:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWHIlR1T15rZzLbiedKXckI158EGNyBUdN3m5oCl0bRrsTIj5mN-do7pxfoHQTQYlizZrojXF4Q9OYwaaf7FkL5Cpss1Zw1VqG5_hBgfpJmc0jBluHk5smM09V1h9aG3ll0pmq2D6kFwbt/s1600/Untitled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWHIlR1T15rZzLbiedKXckI158EGNyBUdN3m5oCl0bRrsTIj5mN-do7pxfoHQTQYlizZrojXF4Q9OYwaaf7FkL5Cpss1Zw1VqG5_hBgfpJmc0jBluHk5smM09V1h9aG3ll0pmq2D6kFwbt/s1600/Untitled.png" /></a></div>
<br />
So we search for the <i>Newtonsoft.Json.dll</i> in folder <b>C:\NugetConfig\First\packages</b> folder as the HintPath is relative from the Dummy.csproj file.<br />
But the build downloads the package to <b>C:\NugetConfig\Second\packages</b><br />
<br />
You can find the code here <a href="https://sites.google.com/site/vtrifonov/share-nuget-packages-between-solutions/NugetConfig-part-1.zip">NugetConfig-part-1.zip</a><b></b><br />
<b><br /></b>
To see how we can resolve this issue go to the <a href="http://vasil-trifonov.blogspot.com/2012/11/share-nuget-packages-between-solutions-part-2.html.html">part 2</a>.</div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none">CodeProject</a>Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-42162149908592636712012-11-05T19:11:00.002+02:002013-09-17T19:08:50.706+03:00Compare two dll files programmatically using Hash<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
In the past days I needed to compare two .NET assemblies to see if they are functionally the same. As you may know, when you build the same project several times the resulting output .dll or .exe file is always different. That's because of the <a href="http://msdn.microsoft.com/en-us/library/system.reflection.module.moduleversionid%28v=vs.110%29.aspx">ModuleVerisonId property</a>(MVID). So in order to check if two assemblies are the same we can disassemble them to <a href="http://en.wikipedia.org/wiki/Common_Intermediate_Language">IL code</a> and compare them.<br />
The easiest way to disassemble the file is to use <a href="http://msdn.microsoft.com/en-us/library/f7dy01k1%28v=vs.80%29.aspx">MSIL Disassembler</a>. However it is not in a common location. It can be in any of the following locations:<br />
<br />
<span style="font-family: Calibri; font-size: small;"><i>C:\Program Files\Microsoft SDKs\Windows\v6.0\bin\ildasm.exe<br />C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe<br />C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe<br />C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\x64\ildasm.exe<br />C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\ildasm.exe<br />C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\x64\ildasm.exe<br />C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe<br />C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\x64\ildasm.exe<br />C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\ildasm.exe<br />C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\x64\ildasm.exe</i></span><br />
<br />
So in my following solution I have embedded the ildasm.exe file in a class library to be able to use it on any machine.When we disassemble an assembly we check if the ildasm.exe file exists in the executing assembly folder and if not the file is extracted there from our dll file.<br />
Using the ildasm file we get the IL code and save it to a temporary file.<br />
Then we need to remove the following three rows:<br />
<ol style="text-align: left;">
<li>MVID - as I wrote before this is a unique GUID generated with every build</li>
<li>Image Base (<span style="color: windowtext;"><i>The image base tells us as to where the program will be loaded in
memory by the Windows loader.</i>) - this is different with every build as well</span></li>
<li><span style="color: windowtext;">Time-date stamp - the time and date when the ildasm is run </span></li>
</ol>
So we read the temp file content, remove these rows we use regex and then save the file content to the same file.<br />
You can find the Disassembler file here:</div>
<pre class="brush: csharp">using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace FileHasher
{
public class Disassembler
{
public static Regex regexMVID = new Regex("//\\s*MVID\\:\\s*\\{[a-zA-Z0-9\\-]+\\}", RegexOptions.Multiline | RegexOptions.Compiled);
public static Regex regexImageBase = new Regex("//\\s*Image\\s+base\\:\\s0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);
public static Regex regexTimeStamp = new Regex("//\\s*Time-date\\s+stamp\\:\\s*0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);
private static readonly Lazy<Assembly> currentAssembly = new Lazy<Assembly>(() =>
{
return MethodBase.GetCurrentMethod().DeclaringType.Assembly;
});
private static readonly Lazy<string> executingAssemblyPath = new Lazy<string>(() =>
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
});
private static readonly Lazy<string> currentAssemblyFolder = new Lazy<string>(() =>
{
return Path.GetDirectoryName(currentAssembly.Value.Location);
});
private static readonly Lazy<string[]> arrResources = new Lazy<string[]>(() =>
{
return currentAssembly.Value.GetManifestResourceNames();
});
private const string ildasmArguments = "/all /text \"{0}\"";
public static string ILDasmFileLocation
{
get
{
return Path.Combine(executingAssemblyPath.Value, "ildasm.exe");
}
}
static Disassembler()
{
//extract the ildasm file to the executing assembly location
ExtractFileToLocation("ildasm.exe", ILDasmFileLocation);
}
/// <summary>
/// Saves the file from embedded resource to a given location.
/// </summary>
/// <param name="embeddedResourceName">Name of the embedded resource.</param>
/// <param name="fileName">Name of the file.</param>
protected static void SaveFileFromEmbeddedResource(string embeddedResourceName, string fileName)
{
if (File.Exists(fileName))
{
//the file already exists, we can add deletion here if we want to change the version of the 7zip
return;
}
FileInfo fileInfoOutputFile = new FileInfo(fileName);
using (FileStream streamToOutputFile = fileInfoOutputFile.OpenWrite())
using (Stream streamToResourceFile = currentAssembly.Value.GetManifestResourceStream(embeddedResourceName))
{
const int size = 4096;
byte[] bytes = new byte[4096];
int numBytes;
while ((numBytes = streamToResourceFile.Read(bytes, 0, size)) > 0)
{
streamToOutputFile.Write(bytes, 0, numBytes);
}
streamToOutputFile.Close();
streamToResourceFile.Close();
}
}
/// <summary>
/// Searches the embedded resource and extracts it to the given location.
/// </summary>
/// <param name="fileNameInDll">The file name in DLL.</param>
/// <param name="outFileName">Name of the out file.</param>
protected static void ExtractFileToLocation(string fileNameInDll, string outFileName)
{
string resourcePath = arrResources.Value.Where(resource => resource.EndsWith(fileNameInDll, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if (resourcePath == null)
{
throw new Exception(string.Format("Cannot find {0} in the embedded resources of {1}", fileNameInDll, currentAssembly.Value.FullName));
}
SaveFileFromEmbeddedResource(resourcePath, outFileName);
}
public static string GetDisassembledFile(string assemblyFilePath)
{
if (!File.Exists(assemblyFilePath))
{
throw new InvalidOperationException(string.Format("The file {0} does not exist!", assemblyFilePath));
}
string tempFileName = Path.GetTempFileName();
var startInfo = new ProcessStartInfo(ILDasmFileLocation, string.Format(ildasmArguments, assemblyFilePath));
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
using (var process = System.Diagnostics.Process.Start(startInfo))
{
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
if (process.ExitCode > 0)
{
throw new InvalidOperationException(
string.Format("Generating IL code for file {0} failed with exit code - {1}. Log: {2}",
assemblyFilePath, process.ExitCode, output));
}
File.WriteAllText(tempFileName, output);
}
RemoveUnnededRows(tempFileName);
return tempFileName;
}
private static void RemoveUnnededRows(string fileName)
{
string fileContent = File.ReadAllText(fileName);
//remove MVID
fileContent = regexMVID.Replace(fileContent, string.Empty);
//remove Image Base
fileContent = regexImageBase.Replace(fileContent, string.Empty);
//remove Time Stamp
fileContent = regexTimeStamp.Replace(fileContent, string.Empty);
File.WriteAllText(fileName, fileContent);
}
public static string DisassembleFile(string assemblyFilePath)
{
string disassembledFile = GetDisassembledFile(assemblyFilePath);
try
{
return File.ReadAllText(disassembledFile);
}
finally
{
if (File.Exists(disassembledFile))
{
File.Delete(disassembledFile);
}
}
}
}
}
</pre>
<br />
So using this class we can get the IL code of a .NET assemlby. Now we can create a Hash Calculator to calculate the hash of the file, so that we can store the hash and compare it with newer files. To calculate the hash I use the following approach:<br />
<ol style="text-align: left;">
<li>Check if the file ends with .dll or .exe - if yes this can be a .NET assembly, if no get file hash</li>
<li>Try to open the assembly with Assembly.LoadFile to see if this is a valid .NET assembly, if there is a BadImageFormatException, this is not a .NET one so get file hash</li>
<li>If this is a .net assembly get the IL code file and calculate it's hash</li>
</ol>
To calculate file hash we use MD5CryptoServiceProvider. So here is the Hash Calculator:<br />
<pre class="brush: csharp">using System;
using System.IO;
using System.Reflection;
namespace FileHasher
{
public class HashCalculator
{
public string FileName { get; private set; }
public HashCalculator(string fileName)
{
this.FileName = fileName;
}
public string CalculateFileHash()
{
if (Path.GetExtension(this.FileName).Equals(".dll", System.StringComparison.InvariantCultureIgnoreCase)
|| Path.GetExtension(this.FileName).Equals(".exe", System.StringComparison.InvariantCultureIgnoreCase))
{
return GetAssemblyFileHash();
}
else
{
return GetFileHash();
}
}
private string GetFileHash()
{
return CalculateHashFromStream(File.OpenRead(this.FileName));
}
private string GetAssemblyFileHash()
{
string tempFileName = null;
try
{
//try to open the assembly to check if this is a .NET one
var assembly = Assembly.LoadFile(this.FileName);
tempFileName = Disassembler.GetDisassembledFile(this.FileName);
return CalculateHashFromStream(File.OpenRead(tempFileName));
}
catch(BadImageFormatException)
{
return GetFileHash();
}
finally
{
if (File.Exists(tempFileName))
{
File.Delete(tempFileName);
}
}
}
private string CalculateHashFromStream(Stream stream)
{
using (var readerSource = new System.IO.BufferedStream(stream, 1200000))
{
using (var md51 = new System.Security.Cryptography.MD5CryptoServiceProvider())
{
md51.ComputeHash(readerSource);
return Convert.ToBase64String(md51.Hash);
}
}
}
}
}
</pre>
You can find the code here <a href="https://sites.google.com/site/vtrifonov/compare-two-dll-files-programmatically-using-hash/AssemblyHasher.zip">AssemblyHasher.zip</a> or on github <a href="https://github.com/vtrifonov/AssemblyHasher">AssemblyHasher</a> </div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com13tag:blogger.com,1999:blog-925946298101307708.post-40149165079509440862012-10-31T19:07:00.001+02:002015-01-14T11:54:39.986+02:00Online Html Encoder<div dir="ltr" style="text-align: left;" trbidi="on">
<style type="text/css">
.textContent
{
height: 200px;
width: 100%;
}
.encodeButton
{
background: none repeat scroll 0 0 #3D9400;
border: 1px solid #29691D;
color: #FFFFFF;
text-shadow: 0 1px rgba(0, 0, 0, 0.1);
border-radius: 2px 2px 2px 2px;
font-size: 11px;
font-weight: bold;
height: 27px;
line-height: 27px;
padding: 0 8px;
text-align: center;
cursor: pointer
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
When using SyntaxHighlighter I need to Html Encode my xml snippets and snippets
containing "<" and/or ">".<br />
That's why I have created this simple form to make the snippets friendly :)<br />
<br />
<div dir="ltr" style="text-align: left;" trbidi="on">
<textarea class="textContent" id="taInput" type="text"></textarea>
<input class="encodeButton" id="bntEncode" type="submit" value="Encode" />
<textarea class="textContent" id="taOutput" type="text"></textarea>
</div>
<script type="text/javascript">
$(document).ready(function () {
$("#bntEncode").on("click", function () {
var taInput = $("#taInput");
var taOutput = $("#taOutput");
taOutput.val(htmlEncode(taInput.val()));
});
});
function htmlEncode(value) {
return $('<div/>').text(value).html();
}
</script>
</div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-71167456446225421002012-10-31T18:20:00.000+02:002012-10-31T18:59:19.201+02:00Remove unneded scrollbars from SyntaxHighlighter<div dir="ltr" style="text-align: left;" trbidi="on">
Some of you may have noticed that when using SyntaxHighlighter version 3.0.83 there are always vertical and horizontal scroll bars no matter what is the size of the text. To fix this problem you have two options.<br />
<ol style="text-align: left;">
<li>Download the shCore.css and change
<pre class="brush: css">.syntaxhighlighter {
width: 100% !important;
margin: 1em 0 1em 0 !important;
position: relative !important;
overflow: auto !important;
font-size: 1em !important;
}
</pre>
to
<pre class="brush: css">.syntaxhighlighter {
width: 100% !important;
margin: 1em 0 1em 0 !important;
position: relative !important;
overflow-y: hidden !important;
overflow-x: auto !important;
font-size: 1em !important;
}</pre>
and reference your version of the file.</li>
<br />
<li>Add the following style in the head section of your template:
<pre class="brush: css"><style type="text/css">
.syntaxhighlighter {
overflow-y: hidden !important;
overflow-x: auto !important;
}
</style>
</pre>
</li>
</ol>
<br />
This will show horizontal scroll bar only when needed.
</div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com1tag:blogger.com,1999:blog-925946298101307708.post-2173595719746177542012-10-30T10:21:00.000+02:002013-09-17T19:05:42.510+03:00WCF Duplex service exceeded timeout of 00:00:00<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
In case you use <a href="http://msdn.microsoft.com/en-us/library/ms731064.aspx">WCF duplex services</a> using wsDualHttpBidning there is a chance to get a very strange error:<br />
<br />
<b><i>The HTTP request to 'http://vasil-trifonov.blogspot.com/DuplexService.svc' has exceeded the allotted timeout of 00:00:00. The time allotted to this operation may have been a portion of a longer timeout.<br />StackTrace: at System.ServiceModel.Channels.HttpChannelUtilities.SetRequestTimeout(HttpWebRequest request, TimeSpan timeout)<br />at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.SendRequest(Message message, TimeSpan timeout)<br />at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)</i></b><br />
<br />
It turns out that the server where the service is hosted did not see the machine that calls the service. So if you get this error make sure that the server can ping the caller.<br />
<br />
Another problem can be that the server may not be able to resolve the temporary listening address as it will be something like<br />
<br />
<i><b>http://trifonov-pc/Temporary_Listen_Addresses/4881e24d-9d46-48dd-a013-399d8fab8757/ccc1d9b8-adbb-479d-a721-517b2162ad6c </b></i><br />
<br />
the remote machine may be in another domain and may not be able to resolve the caller by name. In that case you can use the <a href="http://msdn.microsoft.com/en-us/library/system.servicemodel.wsdualhttpbinding.clientbaseaddress.aspx"><b>clientBaseAddress</b></a> in the caller configuration</div>
<pre class="brush: xml"><wsDualHttpBinding>
<binding name="duplexBinding" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="1073741824"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
clientBaseAddress="http://192.168.0.100/Temporary_Listen_Addresses">
<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" />
<security mode="Message">
<message clientCredentialType="Certificate"/>
</security>
</binding>
</wsDualHttpBinding>
</pre>
We can use the clientBaseAddress to set the IP Address as the base address for the callback channel. We can also set another port there if we want.<br />
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a>
</div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com0tag:blogger.com,1999:blog-925946298101307708.post-4029501495509259732012-10-29T13:14:00.001+02:002013-09-17T19:07:05.675+03:00Securing WCF Service with Self Signed Certificates programmatically<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
I've spent some time to deal with WCF securing with certificates and came to a solution that I want to share.<br />
As you probably know, WCF supports certificate <a href="http://msdn.microsoft.com/en-us/library/ff648360.aspx">authentication</a> and it's not so hard to set up. However you will need to install certificates on both the service machine and the caller machine. This can be a problem if you want to host the service on a shared hosting environment for example. Even if the service is hosted on a machine in your network you will still need some permissions to be given to the service application pool user in order to access the certificate private key.<br />
<br />
So with the help of this <a href="http://blogs.msdn.com/b/dcook/archive/2008/11/25/creating-a-self-signed-certificate-in-c.aspx">blog post</a> I found a way to create Self Signed certificate using some windows native methods.<br />
<br /></div>
<pre class="brush: csharp">using System;
using System.Runtime.InteropServices;
namespace Certificate.Native
{
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FileTimeToSystemTime(
[In] ref long fileTime,
out SystemTime systemTime);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContextW(
out IntPtr providerContext,
[MarshalAs(UnmanagedType.LPWStr)] string container,
[MarshalAs(UnmanagedType.LPWStr)] string provider,
int providerType,
int flags);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(
IntPtr providerContext,
int flags);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGenKey(
IntPtr providerContext,
int algorithmId,
int flags,
out IntPtr cryptKeyHandle);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
IntPtr cryptKeyHandle);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr CertCreateSelfSignCertificate(
IntPtr providerHandle,
[In] ref CryptoApiBlob subjectIssuerBlob,
int flags,
[In] ref CryptKeyProviderInformation keyProviderInformation,
IntPtr signatureAlgorithm,
[In] ref SystemTime startTime,
[In] ref SystemTime endTime,
IntPtr extensions);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CertFreeCertificateContext(
IntPtr certificateContext);
}
}
</pre>
<br />
I have created CertificateSerializer to serialize the certificate to base64string:<br />
<pre class="brush: csharp">using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace Certificate
{
public class CertificateSerializer
{
public X509Certificate2 Deserialize(string certificateString)
{
byte[] numArray = Convert.FromBase64String(certificateString);
string tempFileName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
try
{
File.WriteAllBytes(tempFileName, numArray);
X509Certificate2 certificate = new X509Certificate2(tempFileName, default(string), X509KeyStorageFlags.Exportable);
return certificate;
}
finally
{
File.Delete(tempFileName);
}
}
public string Serialize(X509Certificate2 certificate)
{
byte[] numArray = certificate.Export(X509ContentType.Pfx);
string base64String = Convert.ToBase64String(numArray);
return base64String;
}
}
}
</pre>
<br />
In the configuration section we can store the serialized certificate and the Thumbprints and Subjects of the trusted certificates, so that we can give the service the information about the trusted parties. Here's an example of the custom section content:<br />
<br />
<pre class="brush: xml"> <configSections>
<section name="certificateSection" type="Certificate.Configuration.CertificatesSection, Certificate"/>
</configSections>
<certificateSection certificate="MIIExgIBAzCCBIYGCSqGSIb3DQEHAaCCBHcEggRzMIIEbzCCApgGCSqGSIb3DQEHAaCCAokEggKFMIICgTCCAn0GCyqGSIb3DQEMCgECoIIBjjCCAYowHAYKKoZIhvcNAQwBAzAOBAgbm3IPSqU0BQICB9AEggFojvCBmeSJ6n4IlKxgSv1XgIB5LaD7tb06f/yTLsZRK+4rnqCaesmYFFHP889JTySdqPPyE6fNrpFXTcvcRC6lQQglLnxbRZQotPHvDv4MEzEdI67zkkfM7RsxxXRUQE+ex5H+oQxjScvVRWlKa0KXLk7DOa+Ijz/epLFCum2CE2aUE/AOdi8GCYf7D0yMa472/buQRX1qWX5MYuH+sZI1py/unS8R5R4cytRr8dKJHmn3YtLuhEwQOuXiQ/mUK5PKj+xYp6b8ssVIXQjuLpXZnnT7i/KdZipxmTCf+OtXbAysBw2VaQ9+NmR8cufy8nUb/KgSNfcE3hTHTxIaBnhddhuHxvfR5oYIAzPK3NTq/S1qCEqxDJnBFapdnRcKfHEAlDwIB/KZyHgKdVBiu16pB9e+bxl840CW6vI/tILBbpww3rjvzKKQYZZ6uPu1oNLS2TeX7JsBJE3p0HJE2DPFLfmXLPVPSkHBMYHbMBMGCSqGSIb3DQEJFTEGBAQBAAAAMFcGCSqGSIb3DQEJFDFKHkgAZgAzADEAMwBlADQAYQA4AC0AMAA2ADMANwAtADQANAA1ADAALQBiAGMANAA5AC0AMwA2AGEAMABkAGIAOQAxADkAOABhAGQwawYJKwYBBAGCNxEBMV4eXABNAGkAYwByAG8AcwBvAGYAdAAgAEUAbgBoAGEAbgBjAGUAZAAgAEMAcgB5AHAAdABvAGcAcgBhAHAAaABpAGMAIABQAHIAbwB2AGkAZABlAHIAIAB2ADEALgAwMIIBzwYJKoZIhvcNAQcGoIIBwDCCAbwCAQAwggG1BgkqhkiG9w0BBwEwHAYKKoZIhvcNAQwBBjAOBAjWmEM3BmtPWwICB9CAggGIIxS2KaegZ8TDfdq1AP55giyOzgLOvd1LYA5M1QwRmYcM4IiJe5Z6yB6usrnMa/oAJ6suBw72UTO8lTGc/AXWtbrJg6KM0CuyI7lKdoShn36FRx35djx5plXpDxVrZtR2MbOxgSdUNyUCiuRWe/FUzpwE93IWQnfIleeziH1YXuZdvxy/vTLKT2VngeZh3BjyG25n7Fj44xgy7CQM/g/q+TgHBJjnY9qD36kPdaWxkxytadpJH3GgnKLjoQCvHhFN6NEVhErnvzZo63jPZIDWHxr7EYGkbVTzwtPwlocTDgm75gS/IwCMNdAxHP9ofMM4H+2g/UV88R4ABgUoP139Drz5LrfHFsnvPx3/twygMX6lUccnwyKZTVcphjADHU6FVsm2/xJ/nqxCkiUt7ciz150FqGxJ+vxg5zo533eHjViwdDBHTMIopyypOY69xNfN1VGPMKxfc/d5z6ayKKpi9lXQMIUumoz5Xqjnq4dyschqoUbGNW1LB+0Y3BNHxeXyGlYsTsr9nYowNzAfMAcGBSsOAwIaBBRgiHbVQmQbvNqXli2R3sBoa6AirAQUeAWzhwSRejw9yMIGB2GgBY76bbM=">
<trustedCertificates>
<certificateInfo thumbprint="64123DFA95F03AFB818EC61C874241B62E2A4886" subject="ServiceCertificate"/>
</trustedCertificates>
</certificateSection>
</pre>
<br />
I have created a small windows application for generating certificates and getting it's serialized value and thumbprint:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJmnMU2qmAmcEvdZPLW0p1xnrlVU8bA-Uu5itqSaXWa3z1B-RI8p0bGMCr1rJlwQC0atFfarKvZgCw5hBZ-rtDZiRi42OYaxCdivN0Sy1lplpHncYWLN90aKvmhoDgc63zmmZhVdqen5Jm/s1600/Generate+Cerficate.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJmnMU2qmAmcEvdZPLW0p1xnrlVU8bA-Uu5itqSaXWa3z1B-RI8p0bGMCr1rJlwQC0atFfarKvZgCw5hBZ-rtDZiRi42OYaxCdivN0Sy1lplpHncYWLN90aKvmhoDgc63zmmZhVdqen5Jm/s320/Generate+Cerficate.png" width="320" /></a></div>
Now we will create a custom service behavior extension to take care of the service credentials:
<br />
<pre class="brush: csharp">using System;
using System.ServiceModel.Configuration;
namespace Certificate.Extensions
{
public class CertificateExtensionBehavior : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(CertificateServiceCredentials); }
}
protected override object CreateBehavior()
{
return new CertificateServiceCredentials();
}
}
}</pre>
Here is the CertificateServiceCredentials class which inherits from ServiceCredentials:<br />
<br />
<pre class="brush: csharp">using System.Configuration;
using System.Configuration;
using System.Linq;
using System.ServiceModel.Description;
using Certificate.Configuration;
namespace Certificate.Extensions
{
public class CertificateServiceCredentials : ServiceCredentials
{
public CertificateServiceCredentials()
{
//get the information from our custom configuration section
CertificatesSection certificateSection = (CertificatesSection)ConfigurationManager.GetSection("certificateSection");
var trustedList = certificateSection.TrustedCertificates.Cast<TrustedCertificateInfo>().Select(x => x.Thumbprint);
this.ServiceCertificate.Certificate = certificateSection.Certificate;
//we use custom validation mode to check the response
this.ClientCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.Custom;
//and custom validator to validate if the response's certificate thumbprint is in the trusted thumbprints
this.ClientCertificate.Authentication.CustomCertificateValidator =
new CertificateValidator(trustedList);
}
protected override ServiceCredentials CloneCore()
{
return new CertificateServiceCredentials();
}
}
}
</pre>
<br />
We use a custom certificate validation mode with Certificate validator who checks if the certificate in the response is in the current certificate trusted list. We get the certificate and the trusted list from our custom configuration section. And here's the CertificateValidator:<br />
<pre class="brush: csharp">using System;
using System;
using System.Collections.Generic;
using System.IdentityModel.Selectors;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Security;
namespace Certificate
{
public class CertificateValidator : X509CertificateValidator
{
private readonly IEnumerable<string> trustedThumbprints;
public CertificateValidator(IEnumerable<string> thumbprints)
{
this.trustedThumbprints = thumbprints;
}
public override void Validate(X509Certificate2 certificate)
{
//check if there is a certificate in the request
if (certificate == null)
{
throw new SecurityException("Missing certificate");
}
//check if the certificate thumbprint is in the list of the trusted ones
if (!trustedThumbprints.Any(thumbprint => thumbprint.Equals(certificate.Thumbprint)))
{
throw new SecurityException("The provided certificate is not trusted!");
}
}
}
}
</pre>
<br />
So after that we need to set the service to use this custom extension, so the web.config of the service should look like this:<br />
<pre class="brush: xml"><?xml version="1.0"?>
<configuration>
<configSections>
<section name="certificateSection" type="Certificate.Configuration.CertificatesSection, Certificate"/>
</configSections>
<certificateSection certificate="MIIExgIBAzCCBIYGCSqGSIb3DQEHAaCCBHcEggRzMIIEbzCCApgGCSqGSIb3DQEHAaCCAokEggKFMIICgTCCAn0GCyqGSIb3DQEMCgECoIIBjjCCAYowHAYKKoZIhvcNAQwBAzAOBAiqwSbxN4Q2IQICB9AEggFoaa40+yIrF2Wb3/L4rYE7UpzrhI5S2O8wx72gj41tudES+QiM8DGxC1YWaHx4+THXFkQs62A6eDTKYOZUc8oMUFyVbY70Joq8un6keunCz4xP26MB2vygpx1/ASA6CTdeN3r9JEYss7DGaFnvUJKPH44oyBagaecT3fo0CA+Qa7vfcrlrhcyyovVs5lfJNUm13IF8/bNMCcOdgUnjX/tlay53YDulZSD0kP3apd60zzBAtIr2GD/h3NjiIcjSauDUf7bdvEV0LHAC78mRB/6nUaYiwZhAphky8ufR3dMzZGt5bglbEb8WkEw4bh/qrUxofA5uDmRgjnusAVtOcm0BUvK458bzsyKaRwuw8wSK+Srii5ZYjE5DSTc3msu5jCKZ5pC03w8tdeXmc5Xqq5TziKpDXW1bCa9D/O8mRnz+IsRa1FirDV/Spp37wyucLJkluKHZTpOeTWXRGy1/8Ys5kDxeXAamJxSLMYHbMBMGCSqGSIb3DQEJFTEGBAQBAAAAMFcGCSqGSIb3DQEJFDFKHkgAZAAzADAAMwBkADQAZgA2AC0AYQBmADAANQAtADQANwBjAGUALQBiAGIAZgBjAC0AMwBhAGMANQBlADgAYQBiAGUAOAA3ADcwawYJKwYBBAGCNxEBMV4eXABNAGkAYwByAG8AcwBvAGYAdAAgAEUAbgBoAGEAbgBjAGUAZAAgAEMAcgB5AHAAdABvAGcAcgBhAHAAaABpAGMAIABQAHIAbwB2AGkAZABlAHIAIAB2ADEALgAwMIIBzwYJKoZIhvcNAQcGoIIBwDCCAbwCAQAwggG1BgkqhkiG9w0BBwEwHAYKKoZIhvcNAQwBBjAOBAgatfFYOA+7qAICB9CAggGIOL+SmdG5n6oummdHrr7u0LH7+VwF3rICFqQTncXX9iVTND6DXJArJfFsYGs1fwq4mzTxmrpBArsf0pCht37x5m7m9k/JL/LeXWlh5re+tZptnEl/l/45AUvN3/fMzoaG4rD5keA1POOoir9fVTiiJjvPfIYvriI8siMwx13fyuFYNZlF+T1pkR6WQbRKTYS49nSGxhIgsoUkxXkGm64CRgXRriHqhopDqUmzCgHhE68jjt78Ff9iYl/1KYBvpJfgBTnvV0dNcXcHhmOkOLqHA6ONBFeARH6ous1i2AUoXfTVoFptTb0eSQTrZkravx2uJrSSuMtPP2qOkGkVQNE2TsJdyFVEKwhXuVyhpuDFky56Q73RDzQCfFEhZHfmleUCaZSVJlUXY86b6/Qk4ebzmGyje7+7z29PARHJBKWHJi/759fKmpTMO27gYor+ylFhqz21crjX7uae0jLKg59CjdSgJocpZ5jOK+B4sWqFuEYUMpcUcN3pZ2jkFMqQcWzOinegbwKeMzgwNzAfMAcGBSsOAwIaBBS9S/16xb6qbDTK5fY6EyjfSc/nvgQU1/tVtjNCPOGmYwOUDNupoL7hlpI=">
<trustedCertificates>
<certificateInfo thumbprint="625C675C8C7FF2A4041573116211367DABA71969" subject="CallerCertificate"/>
</trustedCertificates>
</certificateSection>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<certificateExtension />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<services>
<service name="Service.Service">
<endpoint name="myService"
address=""
binding="wsDualHttpBinding"
contract="Service.IService"
bindingConfiguration="certificateBinding"/>
</service>
</services>
<bindings>
<wsDualHttpBinding>
<binding name="certificateBinding">
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsDualHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="certificateExtension" type="Certificate.Extensions.CertificateExtensionBehavior, Certificate"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
</pre>
<br />
And the app.config of the caller should look like this:<br />
<br />
<pre class="brush: xml"><?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="certificateSection" type="Certificate.Configuration.CertificatesSection, Certificate"/>
</configSections>
<certificateSection certificate="MIIExgIBAzCCBIYGCSqGSIb3DQEHAaCCBHcEggRzMIIEbzCCApgGCSqGSIb3DQEHAaCCAokEggKFMIICgTCCAn0GCyqGSIb3DQEMCgECoIIBjjCCAYowHAYKKoZIhvcNAQwBAzAOBAgbm3IPSqU0BQICB9AEggFojvCBmeSJ6n4IlKxgSv1XgIB5LaD7tb06f/yTLsZRK+4rnqCaesmYFFHP889JTySdqPPyE6fNrpFXTcvcRC6lQQglLnxbRZQotPHvDv4MEzEdI67zkkfM7RsxxXRUQE+ex5H+oQxjScvVRWlKa0KXLk7DOa+Ijz/epLFCum2CE2aUE/AOdi8GCYf7D0yMa472/buQRX1qWX5MYuH+sZI1py/unS8R5R4cytRr8dKJHmn3YtLuhEwQOuXiQ/mUK5PKj+xYp6b8ssVIXQjuLpXZnnT7i/KdZipxmTCf+OtXbAysBw2VaQ9+NmR8cufy8nUb/KgSNfcE3hTHTxIaBnhddhuHxvfR5oYIAzPK3NTq/S1qCEqxDJnBFapdnRcKfHEAlDwIB/KZyHgKdVBiu16pB9e+bxl840CW6vI/tILBbpww3rjvzKKQYZZ6uPu1oNLS2TeX7JsBJE3p0HJE2DPFLfmXLPVPSkHBMYHbMBMGCSqGSIb3DQEJFTEGBAQBAAAAMFcGCSqGSIb3DQEJFDFKHkgAZgAzADEAMwBlADQAYQA4AC0AMAA2ADMANwAtADQANAA1ADAALQBiAGMANAA5AC0AMwA2AGEAMABkAGIAOQAxADkAOABhAGQwawYJKwYBBAGCNxEBMV4eXABNAGkAYwByAG8AcwBvAGYAdAAgAEUAbgBoAGEAbgBjAGUAZAAgAEMAcgB5AHAAdABvAGcAcgBhAHAAaABpAGMAIABQAHIAbwB2AGkAZABlAHIAIAB2ADEALgAwMIIBzwYJKoZIhvcNAQcGoIIBwDCCAbwCAQAwggG1BgkqhkiG9w0BBwEwHAYKKoZIhvcNAQwBBjAOBAjWmEM3BmtPWwICB9CAggGIIxS2KaegZ8TDfdq1AP55giyOzgLOvd1LYA5M1QwRmYcM4IiJe5Z6yB6usrnMa/oAJ6suBw72UTO8lTGc/AXWtbrJg6KM0CuyI7lKdoShn36FRx35djx5plXpDxVrZtR2MbOxgSdUNyUCiuRWe/FUzpwE93IWQnfIleeziH1YXuZdvxy/vTLKT2VngeZh3BjyG25n7Fj44xgy7CQM/g/q+TgHBJjnY9qD36kPdaWxkxytadpJH3GgnKLjoQCvHhFN6NEVhErnvzZo63jPZIDWHxr7EYGkbVTzwtPwlocTDgm75gS/IwCMNdAxHP9ofMM4H+2g/UV88R4ABgUoP139Drz5LrfHFsnvPx3/twygMX6lUccnwyKZTVcphjADHU6FVsm2/xJ/nqxCkiUt7ciz150FqGxJ+vxg5zo533eHjViwdDBHTMIopyypOY69xNfN1VGPMKxfc/d5z6ayKKpi9lXQMIUumoz5Xqjnq4dyschqoUbGNW1LB+0Y3BNHxeXyGlYsTsr9nYowNzAfMAcGBSsOAwIaBBRgiHbVQmQbvNqXli2R3sBoa6AirAQUeAWzhwSRejw9yMIGB2GgBY76bbM=">
<trustedCertificates>
<certificateInfo thumbprint="64123DFA95F03AFB818EC61C874241B62E2A4886" subject="ServiceCertificate"/>
</trustedCertificates>
</certificateSection>
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding name="certificatesBinfing">
<security mode="Message">
<message clientCredentialType="Certificate"/>
</security>
</binding>
</wsDualHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:9986/Service.svc" binding="wsDualHttpBinding"
bindingConfiguration="certificatesBinfing" contract="IService"
name="BasicHttpBinding_IService">
</endpoint>
</client>
</system.serviceModel>
</configuration>
</pre>
<br />
I have added a small WCF Extensions helper class from <a href="http://www.codeproject.com/Tips/197531/Do-not-use-using-for-WCF-Clients">here</a>.<br />
<br />
<pre class="brush: csharp">using System;
using System.ServiceModel;
namespace Caller.Proxy
{
public static class WcfExtensions
{
public static void Using<T>(this T client, Action<T> work)
where T : ICommunicationObject
{
try
{
work(client);
client.Close();
}
catch (CommunicationException)
{
client.Abort();
}
catch (TimeoutException)
{
client.Abort();
}
catch (Exception)
{
client.Abort();
throw;
}
}
}
}
</pre>
<br />
Now the only thing we need to do is to call the service from the caller:<br />
<pre class="brush: csharp">using System;
using System.Configuration;
using System.Linq;
using System.ServiceModel;
using Caller.Proxy;
using Certificate;
using Certificate.Configuration;
namespace Caller
{
class Program
{
static void Main(string[] args)
{
new ServiceClient().Using(channel =>
{
//get the information from our custom configuration section
CertificatesSection certificateSection = (CertificatesSection)ConfigurationManager.GetSection("certificateSection");
var trustedList = certificateSection.TrustedCertificates.Cast<trustedcertificateinfo>().ToList();
var endpointAddress = channel.Endpoint.Address.Uri;
//get the first trusted certification. We assume that you will call only one service, so we will have only one item in the TrustedCertificates
//this can be improved to select which service do you want to call an what is the response thumbprint that we expect
string trustedSubject = trustedList.FirstOrDefault().Subject;
//we create a dns identity from the trusted object to be able to authenticate with the service
//<identity>
// <dns value="trustedsubject">
//</dns></identity>
var identity = EndpointIdentity.CreateDnsIdentity(trustedSubject);
channel.Endpoint.Address = new EndpointAddress(endpointAddress, identity);
channel.ClientCredentials.ClientCertificate.Certificate = certificateSection.Certificate;
//set custom validation mode
channel.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.Custom;
//trust results only from the certificate in the trusted list
channel.ClientCredentials.ServiceCertificate.Authentication.CustomCertificateValidator = new CertificateValidator(trustedList.Select(x => x.Thumbprint));
var data = channel.GetData(1);
Console.WriteLine(data);
});
}
}
}
</trustedcertificateinfo></pre>
And that's it. We can create new certificate with the generator, set the serialized value in the configuration file of one of the sides and add the certificate subject and thumbprint in the other side's trusted certificates and vice versa.<br />
<br />
<span style="color: red;">If you host the service and the site on IIS you need to set IIS Application Pool configuration (Application Pools > Advanced
Settings) to load the user profile for the application pool identity
user.</span><br />
<span style="color: red;">Otherwise the user may not be able to load the certificate. </span><br />
<br />
You can find the example project here <a href="https://sites.google.com/site/vtrifonov/securing-wcf-service-with-self-signed-certificates-programmatically-example/WCF_Certificates.zip">WCF_Certificates.zip</a></div>
<a href="http://www.codeproject.com/script/Articles/BlogFeedList.aspx?amid=9557117" rel="tag" style="display: none;">CodeProject</a></div>
Vasil Trifonovhttp://www.blogger.com/profile/07795153763056272901noreply@blogger.com1