Code musings in C# https://www.thomasb.fr My coding journey and discovery Fri, 28 Oct 2022 08:16:34 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 120235789 Migrate YUI components https://www.thomasb.fr/2022/10/migrate-yui-components/ https://www.thomasb.fr/2022/10/migrate-yui-components/#respond Fri, 28 Oct 2022 08:16:34 +0000 https://www.thomasb.fr/?p=498 Continue reading Migrate YUI components]]> I work on an application that uses quite a lot of Javascript components based on the YUI framework, which has been obsolete since around 2014. We recently had the budget to start migrating them to plain Javascript.

How YUI widgets work

Our YUI components use the syntax of YUI3. Sometimes they also use widgets from YUI2 : these have to be rebuilt, or migrated to use another library.

A YUI 3 component is structured as follows:

// component declaration
YGlobal.add('mycomponent', function (Y) {
    // object constructor
    function MyComponent() { }

    // static method
    MyComponent.Foo = function() {
		doSomething();
    };
	
    // extends the YUI components
    Y.extend(MyComponent, Y.Base, {
        // initializes the component
        initializer: function(config) {
            this.name = config.name;
        },
		
        // destroys the component
        destructor: function() { },

        // instance method
        foo: function() { }
    });

    // adds the component to the namespace
    Y.namespace('mycompany').MyComponent = MyComponent;

    // component version
    }, '0.1',
    {
        // other YUI components
        requires: ['list', 'of', 'used', 'components']
    }
);

The component is called this way:

// the "use" loads the file if it's not already
YGlobal.use('mycomponent', function (Y) {
    // you can now use static methods
    Y.mycompany.MyComponent.Foo('bar');
	
    // or create an instance
    let mc = new Y.mycompany.MyComponent();
    mc.foo();
});

Migration

Migrating the components implies a lot of steps. The first and easiest, is to replace the YUI methods inside with native or equivalent methods. As a side note, we’re using JQuery in the application : much easier than native JS for querying DOM, creating elements, making ajax calls, etc.

The Y.use('foo', function (X) {}) can be removed, and calls to X.foo.bar() will be replaced with calls to the migrated “foo” component. Warning: existing return inside the Y.use were just breaking out of the use, they now break out of the wrapping method. doc

Y.JSON becomes just JSON. doc

Y.log becomes console.log, or more often nothing. doc

Y.each(foo, function (bar) can be replaced with $.each(foo, function (index, bar), or just for (let bar in foo) in native JS ; take care, Y.each takes a second parameter for the execution context, but not $.each : calls to this inside the Y.each will have to be replaced. doc YUI ; doc Jquery

Y.UA can be switched to equivalents with UAParser ; that said, try not to use it at all if you can. doc

Y.Cookie can be remplaced by equivalents using js-cookie (managing cookies in JS is a pain) ; it so happens that get, set and remove methods exist in both librairies, so it’s often just replacing Y.Cookie with Cookies. doc

Y.Lang.isValue is actually pretty useful, and I chose to copy its source. doc ; source

Y.Lang.isArray is replaced with Array.isArray.

Y.QueryString can be replaced with URLSearchParams : const urlParams = new URLSearchParams(location.search);

AJAX calls

Y.io (doc) is replaced with $.ajax (doc) which works similarly, but the signature differences are pretty important.

If you need blocking calls, sync: true becomes async: false ; but if you can, making async calls with a callback is a better idea.

The object parameter { on: { success: function(id, xhr, args) {}, failure: function(id, xhr, args) {}, context: this } } “goes up a level”, context is removed, and failure is renamed error : { success: function(data, status, xhr) {}, error: function(xhr, status, error) {} }.

You can replace headers: { 'Content-Type': 'application/json' } with contentType: 'application/json' but I think both work.

You can also remplace the success/error methods with promises, for instance Y.io('foo', { success: function() {} }) becomes $.ajax('foo').done(function() { }).

In the method success/done :

  • the signature goes from id, xhr, args to data, status, xhr
  • the data object can be used directly, rather than parsing xhr: if it’s JSON, it’s already been parsed, you need to remove JSON.parse(xhr.responseText).

The method error/fail also changes signature (from id, xhr to xhr, status, errorThrown).

Node operations

The base YUI selector Y.one(foo) becomes $(foo)doc YUI ; doc Jquery

All variable usage will then have to be modified:

  • YUI returns null if no element is found, whereas JQuery returns an object with a zero length property: you’ll have to change your tests on empty values.
  • .appendChild becomes.append
  • .one becomes.find
  • Y.Node.create('foo') becomes $('<foo></foo>')
  • .set('text', x) becomes .text(x)
  • .set('innerHTML', x) becomes .html(x)

Take especially care if the DOM object is exposed by the component : other parts of your application will have to be changed, too.

Final template

Here is the template we’re now using to create JS components/widgets.

mycompany = mycompany || {};

// OPTIONAL : Jquery plugin
$.fn.MyComponent = function (options) {
	// do something with this
	var instance = new mycompany.MyComponent(options);

	return this;
};

// constructor
mycompany.MyComponent = function MyComponent(settings) {
	var that = this;

	settings = $.extend({
		name: '',
		nodeId: ''
	}, settings);

	that._settings = settings;
	that._name = settings.name;
	that._nodeId = settings.nodeId;
    
    that.render();

	return that;
};

// render the control
mycompany.MyComponent.prototype.render = function render() {
	// TODO
};

// sample static method
mycompany.MyComponent.Foo = function Foo() {
	// TODO
};
]]>
https://www.thomasb.fr/2022/10/migrate-yui-components/feed/ 0 498
Unable to type text in inputs in IE 11, missing caret https://www.thomasb.fr/2018/10/unable-to-type-text-in-inputs-in-ie-11-missing-caret/ https://www.thomasb.fr/2018/10/unable-to-type-text-in-inputs-in-ie-11-missing-caret/#respond Wed, 31 Oct 2018 16:53:42 +0000 http://thomasb.fr/?p=330 Continue reading Unable to type text in inputs in IE 11, missing caret]]> documented here but it looks like it should have been fixed in 2014, and I’m using Windows 10 (so one would assume it has this fix). Anyway, the bug points towards a focused element being removed from the DOM. I have tried to just focus  something else and then blur  it, but it didn’t work. My salvation came when I realized that the content of the popup was inside an iframe. Selecting and bluring the iframe’s body did the trick :
$('#modalWindow').find('iframe').contents().find('body').focus().blur()
  ]]>
https://www.thomasb.fr/2018/10/unable-to-type-text-in-inputs-in-ie-11-missing-caret/feed/ 0 330
Always run Visual Studio as administrator on Windows 10 https://www.thomasb.fr/2018/09/always-run-visual-studio-as-administrator-on-windows-10/ https://www.thomasb.fr/2018/09/always-run-visual-studio-as-administrator-on-windows-10/#respond Fri, 21 Sep 2018 08:37:01 +0000 http://thomasb.fr/?p=323 Continue reading Always run Visual Studio as administrator on Windows 10]]> You might be working with a program that always require administrator privileges, such as a service listening on a port. Up to Windows 10, you just right clicked the devenv exe, and checked “always run as administrator” in the compatibility tab. But in Windows 10, the compatibility tab is missing for the Visual Studio exe. Why? No idea. But here’s how to fix it.

To just run the shortcut as admin : right click on the sortcut > in the “properties” tab, click “advanced”, then check “always run as administrator”. Simple, fast, easy.

If you want to run Visual Studio as administrator also when opening a .sln or .csproj file, it’s not so easy. Here are the logical, coherent, and easy to find steps to fix this: (labels translated to english from french, sorry if it doesn’t match)

  • Open the start menu, type “troubleshoot”, and open “check your computer state and solve problems”
  • On the bottom left, click “troubleshoot compatibility problems”
  • Click on “advanced” on the left then “run as administrator”
  • Click again on “advanced”, then uncheck “automatically fix”, then “next”
  • Search for Visual Studio in the list, then “next”
  • Make sure “fix the program” is checked, then “next”
  • Select “troubleshoot the program” (bottom option)
  • Check “the program requires additional rights”, then “next”
  • Click “test the programe” then “next”
  • Select “save the parameters for the program” then “close”

Easy, logical, simple. Thanks Microsoft!

]]>
https://www.thomasb.fr/2018/09/always-run-visual-studio-as-administrator-on-windows-10/feed/ 0 323
Git-svn and branches https://www.thomasb.fr/2018/07/git-svn-and-branches/ https://www.thomasb.fr/2018/07/git-svn-and-branches/#respond Tue, 24 Jul 2018 08:57:17 +0000 http://thomasb.fr/?p=320 Continue reading Git-svn and branches]]> I’m using git-svn to leverage the power of git while the rest of the company prefers the simplicity of svn.

I have created a local branch, intending to merge it in the trunk/master when I’m done, but it turns out it’s much more complicated than expected. So, I want to commit it on svn.
I already have checked out the full repository.

The most simple solution I have found; all other solutions fiddle with the git config files, and I don’t like it:

  • Checkout the branch: git checkout mybranch
  • Rename the local branch: git branch -m mybranch-temp
  • Create a remote branch “mybranch” in SVN using Tortoise SVN
  • Fetch the SVN repository to get the new branch: git svn fetch
  • Check that the new SVN branch has been fetched: git branch -r (or git branch -avv )
  • Get back to the master branch: git checkout master
  • Checkout the remote branch: git checkout -b mybranch-svn origin/mybranch
  • Rebase my local branch into the local svn branch: git rebase mybranch-temp
  • Commit the branch in svn: git svn dcommit ; use --dry-run  to check the branch it will commit to, because sometimes it still commits to the trunk (I haven’t found out why).
]]>
https://www.thomasb.fr/2018/07/git-svn-and-branches/feed/ 0 320
Unit testing MongoDB queries https://www.thomasb.fr/2017/12/unit-testing-mongodb-queries/ https://www.thomasb.fr/2017/12/unit-testing-mongodb-queries/#respond Wed, 20 Dec 2017 11:03:59 +0000 http://thomasb.fr/?p=302 Continue reading Unit testing MongoDB queries]]> When writing unit tests, it’s common to stop at the database access layer.
You might have a “dumb” data access layer that just passes stored procedure names and parameters to the SQL database, for instance.
It’s usually very hard to test this layer, since it requires either that your build server has access to a SQL Server instance, or that a SQL Server is running on the build server.
Plus, we’re getting out of unit tests and are entering integration tests, here.

Using a more advanced tool like Entity Framework, in order to test your complex EF queries, there are usually methods to insert fake data into a fake container, like Test Doubles, InMemory, or Effort.

Using MongoDB, you might encounter the same problem : how do I test that my complex queries are working ?

Here for instance, I get the possible colors of a product; how do I know it works using unit tests?

public IEnumerable<Color> GetColors(string productId) {
	var coll = db.GetCollection<BsonDocument>("products");
	var aggregate = coll.Aggregate(new AggregateOptions { AllowDiskUse = true })
		.Match(new BsonDocument { { "ProductId", productId }, { "Color", new BsonDocument("$ne", "") } })
		.Unwind(new StringFieldDefinition<BsonDocument>("Color"))
		.Group(new BsonDocument { { "_id", "$Color" }, { "Count", new BsonDocument("$sum", 1) } })
		.Sort(Builders<BsonDocument>.Sort.Descending("Count"))
		.Limit(100);

	return aggregate
		.ToList()
		.Select(doc => new Color { ColorValue = doc["_id"].AsString, Count = doc["Count"].AsInt32 });
}

MongoDB has an “inMemory” storage engine, but it’s reserved to the (paid) Enterprise edition. Fortunately, since 3.2, even the Community edition has a not-very-well-documented “ephemeralForTests” storage engine, which loads up an in-memory Mongo instance, and does not store anything on the hard drive (but has poor performances). Exactly what we need!

Before running the data access layer tests, we will need to fire up an in-memory instance of MongoDB.
This instance will be common to all the tests for the layer, otherwise the test runner will fire up new tests faster than the system releases resources (file and ports locks).

You will have to extract the MongoDB binaries in your sources repository somewhere, and copy them besides your binaries on build.

The following wrapper provides a “Query” method that allows us to access the Mongo instance through command-line, bypassing the data access layer, in order to insert test data or query insertion results.

public class MongoDaemon : IDisposable {
	public const string ConnexionString = "mongodb://localhost:27017";
	public const string DbName = "test";
	public const string Host = "localhost";
	public const string Port = "27017";
	private readonly string assemblyFolder;
	private readonly string dbFolder;
	private readonly string mongoFolder;
	private Process process;

	public MongoDaemon() {
		this.assemblyFolder = Path.GetDirectoryName(new Uri(typeof(MongoDaemon).Assembly.CodeBase).LocalPath);
		this.mongoFolder = Path.Combine(this.assemblyFolder, "mongo");
		this.dbFolder = Path.Combine(this.mongoFolder, "temp");

		// re-create db folder if it exists
		if (Directory.Exists(this.dbFolder)) {
			Directory.Delete(this.dbFolder, true);
			Directory.CreateDirectory(this.dbFolder);
		}

		this.process = new Process();
		process.StartInfo.FileName = Path.Combine(this.mongoFolder, "mongod.exe");
		process.StartInfo.Arguments = "--dbpath " + this.dbFolder + " --storageEngine ephemeralForTest";
		process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
		process.Start();
	}

	public void Dispose() {
		Dispose(true);
		GC.SuppressFinalize(this);
	}

	public string Query(string query) {
		var output = string.Empty;

		var procQuery = new Process {
			StartInfo = new ProcessStartInfo {
				FileName = Path.Combine(this.mongoFolder, "mongo.exe"),
				Arguments = string.Format("--host {0} --port {1} --quiet --eval \"{2}\"", Host, Port, query),
				UseShellExecute = false,
				RedirectStandardOutput = true,
				CreateNoWindow = true
			},
		};

		procQuery.Start();

		// read query output
		while (!procQuery.StandardOutput.EndOfStream) {
			output += procQuery.StandardOutput.ReadLine() + "\n";
		}

		// wait 2 seconds max before killing it
		if (!procQuery.WaitForExit(2000)) {
			procQuery.Kill();
		}

		return output;
	}

	protected virtual void Dispose(bool disposing) {
		if (disposing) {
			// dispose managed resources
		}

		if (process != null && !process.HasExited) {
			process.Kill();
		}
	}
}

We’re using the IClassFixture  interface of xUnit to fire up a MongoDaemon instance that will be common to all our tests using it.
It means we need to clean up previously inserted test data at each run.

public class MongoDataStoreTests : IClassFixture<MongoDaemon> {
	private readonly MongoDaemon daemon;
	private readonly DataStore sut;

	public MongoDataStoreTests(MongoDaemon daemon) {
		this.daemon = daemon;
		this.sut = new DataStore("localhost", 27017);

		// cleanup previous tests data
		this.daemon.Query("db.products.drop()");
	}

	[Fact]
	public void Colors_are_counted() {
		// arrange : insert test data
		var id = "test";
		var nb = 5;
		for (int i = 0; i < nb; i++) {
			var color = string.Format("{0}{0}{0}", i, i, i);
			var data = "{ ProductId: '" + id + "', Color: '" + color + "' }";
			this.daemon.Query("db.products.insertOne(" + data + ")");
		}

		// act
		var result = this.sut.GetColors(id);

		// assert
		result.Should().HaveCount(nb);
	}
}

There you have it: a kind-of-easy way to test your Mongo data access layer.

]]>
https://www.thomasb.fr/2017/12/unit-testing-mongodb-queries/feed/ 0 302
Nested sets references https://www.thomasb.fr/2017/04/nested-sets-references/ https://www.thomasb.fr/2017/04/nested-sets-references/#respond Thu, 13 Apr 2017 10:15:32 +0000 http://thomasb.fr/?p=288 Continue reading Nested sets references]]> Just bookmarking a few things here.

]]>
https://www.thomasb.fr/2017/04/nested-sets-references/feed/ 0 288
Modify copy/paste format depending on target https://www.thomasb.fr/2016/02/modify-copypaste-format-depending-on-target/ https://www.thomasb.fr/2016/02/modify-copypaste-format-depending-on-target/#respond Mon, 29 Feb 2016 16:28:55 +0000 http://thomasb.fr/?p=270 Continue reading Modify copy/paste format depending on target]]> Sometimes you want to copy tabular data, and paste it differently depending on the target. For instance, you want CSV when you paste in your text editor, and HTML when you paste it in an email.

Fortunately, some nice fellow has created a helper class to handle all that for you. And it’s even available on GitHub!

To sum it up, the Clipboard  class has a SetDataObject method. A DataObject can get multiple contents (with different DataFormats) through multiple calls of the SetData(string, object)  method.
So basically, you should create a DataObject, then call SetData once with the HTML, and once with text. The trick is that the HTML needs special formatting to work.

]]>
https://www.thomasb.fr/2016/02/modify-copypaste-format-depending-on-target/feed/ 0 270
Start a WPF application in the notification area with Caliburn.Micro https://www.thomasb.fr/2015/08/start-a-wpf-application-in-the-notification-area-with-caliburn-micro/ https://www.thomasb.fr/2015/08/start-a-wpf-application-in-the-notification-area-with-caliburn-micro/#comments Mon, 24 Aug 2015 13:45:10 +0000 http://thomasb.fr/?p=264 Continue reading Start a WPF application in the notification area with Caliburn.Micro]]> Sometimes you’re writing a quick utility app that you wish to start directly in the notification area (near the clock), and not display anything on startup.
It’s actually very simple using Caliburn.Micro.

You probably already have customized your application bootstrapper already. All you have to do is modify the OnStartup  method from DisplayRootViewFor<T>  to displaying a custom tooltip. Here I’m using the Hardcodet.NotifyIcon.Wpf Nuget package:

protected override void OnStartup(object sender, StartupEventArgs e)
{
	TaskbarIcon tbi = new TaskbarIcon();
	tbi.Icon = Properties.Resources.sql_icon;
	tbi.ToolTipText = Properties.Resources.ToolTip;
}

It seems obvious in retrospect, but it took me a while to find this, because I don’t modify the app bootstrapper often, so I forgot about this method.

Don’t forget to add behavior to this icon! Double-click, context menu…

I have created a Caliburn.Micro + MahApps template app if you wish a starting point : https://github.com/cosmo0/Caliburn.MahApps.Metro.Template

]]>
https://www.thomasb.fr/2015/08/start-a-wpf-application-in-the-notification-area-with-caliburn-micro/feed/ 4 264
Mapping DataTables to domain objects https://www.thomasb.fr/2015/06/mapping-datatables-to-domain-objects/ https://www.thomasb.fr/2015/06/mapping-datatables-to-domain-objects/#respond Wed, 17 Jun 2015 08:47:44 +0000 http://thomasb.fr/?p=261 Continue reading Mapping DataTables to domain objects]]> It’s always a pain to work with DataTables and DataSets. You’re always typing these pesky column names, it doesn’t necessarily represents the actual data model (if there are nested data), there is no type safety until runtime, it’s pretty hard to use Linq on your results… the list goes on.

Fortunately, there is a simple solution: AutoMapper. It’s very simple to cast a DataTable to a list of objects:

public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Person> people = AutoMapper.Mapper.DynamicMap<IDataReader, List<Person>>(
    sourceDataTable.CreateDataReader());

]]>
https://www.thomasb.fr/2015/06/mapping-datatables-to-domain-objects/feed/ 0 261
Mocking static methods https://www.thomasb.fr/2015/06/mocking-static-methods/ https://www.thomasb.fr/2015/06/mocking-static-methods/#respond Fri, 05 Jun 2015 16:45:47 +0000 http://thomasb.fr/?p=257 Continue reading Mocking static methods]]> I’m working on a pretty old application with no unit tests whatsoever, and I’m trying to write some as I add features and fix bugs.

After successively finding out about MS Fakes, then that it’s only included in VS2012+ Premium/Ultimate and very hard to include in a continuous integration chain, I was pretty stumped until I found this, which works incredibly well.

Another option to transform the static method into a static Func or Action. For instance.

Original code:

class Math
{
    public static int Add(int x, int y)
    {
        return x + y;
    }
}

You want to “mock” the Add method, but you can’t. Change the above code to this:

public static Func&lt;int, int, int&gt; Add = (x, y) =&gt;
{
    return x + y;
};

Existing client code doesn’t have to change (maybe recompile), but source stays the same.

Now, from the unit-test, to change the behavior of the method, just reassign an in-line function to it:

[TestMethod]
public static void MyTest()
{
    Math.Add = (x, y) =&gt;
    {
        return 11;
    };
}

Put whatever logic you want in the method, or just return some hard-coded value, depending on what you’re trying to do.

This may not necessarily be something you can do each time, but in practice, I found this technique works just fine.

]]>
https://www.thomasb.fr/2015/06/mocking-static-methods/feed/ 0 257