Electron main process debugging with Visual Studio Code

It turned out that debugging Electron’s main process is not trivial. I did not manage to make it work with node-inspector. Surprisingly, I turned out that it is relatively easy to set up end to end Electron development environment with Visual Studio Code. All you really need is create default “launch.json” file by using the cog icon in debugging tab. With the default setting you should be to attach to a running electron app but you can not use “Launch” profile with its default content. You have to specify electron.exe location in “runtimeExecutable” – just like on the screenshot below.

image-4

Once you have done that, you will be able to start your Electron app straight from debugging tab in your Code window.

So when you start debugging using “Launch” profile (not “Attach” nor “Attach to process”), you will notice that your application is running…

image-5

… and your breakpoints are being hit too.

image-3

Which makes Visual Studio Code pretty neat IDE for designing electron.atom.io powered desktop applications.

You can find full launch.json code I have used with my setup (electron quick-start sample app) attached below:

{
“version”: “0.2.0”,
“configurations”: [
{
“name”: “Launch”,
“type”: “node”,
“request”: “launch”,
“program”: “${workspaceRoot}\\main.js”,
“stopOnEntry”: false,
“args”: [],
“cwd”: “${workspaceRoot}”,
“preLaunchTask”: null,
“runtimeExecutable”: “${workspaceRoot}\\node_modules\\electron\\dist\\electron.exe”,
“runtimeArgs”: [
],
“env”: {
},
“externalConsole”: false,
“sourceMaps”: false,
“outDir”: null,

“port”: 5858,
“address”: “localhost”,
},
{
“name”: “Attach”,
“type”: “node”,
“request”: “attach”,
“port”: 5858,
“address”: “localhost”,
“restart”: false,
“sourceMaps”: false,
“outDir”: null,
“localRoot”: “${workspaceRoot}”,
“remoteRoot”: null
},
{
“name”: “Attach to Process”,
“type”: “node”,
“request”: “attach”,
“processId”: “${command.PickProcess}”,
“port”: 5858,
“sourceMaps”: false,
“outDir”: null
}
]
}

Data mining opportunities for small and mid companies

The following article is also published on my new website.

There is a famous anecdote about research done by Wal-Mart about how upcoming hurricane impacts their customers’ purchases (http://www.nytimes.com/2004/11/14/business/yourmoney/what-walmart-knows-about-customers-habits.html).

We didn’t know in the past that strawberry Pop-Tarts increase in sales, like seven times their normal sales rate, ahead of a hurricane

Ms. Dillman

Wal-Mart is a real giant. It is not surprising that marketing guys working there spend big bucks on data mining so why smaller companies would bother?

3D render of battle in a virtual world / Digital war concept or attackThe answer is: because they can afford, they can benefit from it, there are right tools to be used and people who know how to use them.

To be fair, that is what I love about IT – it allows small businesses to grow, it allows new companies to pop up in areas unreachable for them few years in the past. Think how IT technologies enabled companies around the globe reaching the “long tail” (https://en.wikipedia.org/wiki/Long_tail).

The other reason for small and mid companies to consider data mining is that the data is already there. In most of the cases you do not have to run expensive researches and queries. As more and more of operations are handled by computers, there is relatively little effort involved with getting this data ready for data mining. In addition, the storage is really cheap which makes data acquisition even cheaper and more available. In fact, I have seen very small companies collecting gigabytes (sometimes more) of data and not appreciating the great opportunities.

And the tools! There are many decent tools out there – some of them available for FREE (amazing Weka platform by University of Waikato  in New Zeland – http://www.cs.waikato.ac.nz/ml/weka/ or R – https://www.r-project.org).

If you represent a small to mid enterprise and you think you can benefit from data mining – give me a buzz.

Quartz.NET – remote scheduler without job assembly reference on the client side

I have seen few tutorials showing how to schedule Quartz.NET jobs on the remote process using scheduler proxy and all of them suffered th e same inconvenience – they assumed that jobs assembly is added as a reference to the both client and a server projects. In my opinion this is a serious architecture smell because jobs are most certainly part of your business logic (in the scenarios I can think of) and it can cause problems with deployment (imagine one server running Quartz.NET scheduler and few or dozens of clients) and could cause some security issues (disassembling etc.). I really think that jobs should be placed in the business logic, not visible to clients which should be able to schedule them using some kind of contract / interface.

Here is how to achieve this.

Job contract – referenced by client and server side. Exposes only information required to identify a job and prepare its parameters:

//contract for the job - simple class with constant unique job name and some helper method
//used to create parameters for the job instance
public class SomeJobContract
{
	public const string JobName = "SomeApi.SomeJobUniqueName";

	public const string SomeParameterName = "BusinessObjectId";

	public static IDictionary<string, object> BuildJobDetails(int businessObjectId)
	{
		return new Dictionary<string, object>()
		{
			{ SomeParameterName, businessObjectId }
		};
	}
}

Job implementation – its type can not be resolved on the client side because there is no reference to the implementation assembly:

//implementation of the job which contract is exposed
public class SomeJob : IJob
{
	public void Execute(IJobExecutionContext context)
	{
		//use data passed with trigger - context.Trigger instead of context.JobDetails.JobDataMap
		var businessObjectId = (int?)context.Trigger.JobDataMap[SomeApi.SomeJobContract.SomeParameterName];
		
		//... regular job code
	}
}

Server code used to register a specific job (using its type) with a unique identifier exposed in contract and hence known to the client:

//create job details - use unique job identifier
var preProcessingJob = JobBuilder.Create<SomeJob>()
                .StoreDurably(true)
                .RequestRecovery(true)
                .WithIdentity(SomeApi.SomeJobContract.JobName)
                .Build();

//add a durable job to scheduler without using a trigger
scheduler.AddJob(preProcessingJob, true, true);

Client side code used to schedule the job with appropriate parameters – uses only information exposed by the contract assembly:

//create a trigger for the job with specified identifier (we use contract, we have no reference to job implementation on the client side)
var trigger = TriggerBuilder
                .Create()
                .ForJob(SomeApi.SomeJobContract.JobName)
                .UsingJobData(new JobDataMap(SomeApi.SomeJobContract.BuildJobDetails(myBusinessObjectId)))
                .WithSimpleSchedule().StartNow()
                .Build();

schedulerProxy.ScheduleJob(trigger);

jQuery AJAX – redirect on unathorised request

Problem

In default ASP.NET MVC setup, when you send a AJAX request to the MVC Action which returns JSON or simple type value (boolean / string) and the request is not authenticated (user has just logged out or authentication cookie has expired) your jQuery success callback will be fired with a login page HTML content as data. That happens because default AuthorizeAttribute redirects unauthenticated requests to he login page, and jQuery follows this redirect and does not provide a simple way of telling what just happened nor why the redirect was performed.

Solution

To overcome this inconvenience you can roll custom AuthorizeAttribute which will redirect standard HTML requests (caused by forms posting and browser url change) to the login change but return an error code (which can be detected on the jQuery script side) for AJAX request.

public class AJAXAwareAuthorizeAttribute : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
                filterContext.Result = new HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            else
                base.HandleUnauthorizedRequest(filterContext);
        }
    }

 

Then you can add a global AJAX handler to check for the 403 (Forbidden) code on each AJAX request and redirect/show error/do anything else when this code is returned from the controller.

$(document).ajaxError(function (event, xhr, settings, error) {
    //when there is an AJAX request and the user is not authenticated -> redirect to the login page
    if (xhr.status == 403) { // 403 - Forbidden
        window.location = '/login';
    }
});
 Now your success callback will not fire for unauthenticated requests and the error callback will be triggered instead

jQuery – how to get the validation framework to work with hidden fields

Problem

jQuery validation does not validate hidden fields by default, even when appropriate attributes (data-val, data-val-required etc.) are applied.

Solution

You can change that by overwriting jQuery validation defaults. There is a ignore field which is set to :hidden which causes all hidden elements to be skipped when validation happens.

If you want only specific hidden fields to get validated you can do something like this:

$.validator.setDefaults({
        ignore: ":hidden:not([data-validate-hidden])"
    });

 

This will cause all hidden fields to be ignored by validation framework unless they have data-validate-hidden attribute.

Offline Pessimistic Lock in Entity Framework (or any other ORM)

I’ve always found surprising that so few projects were started with data access concurrency in mind. I’ve heard many discussions about new fancy frameworks and UI controls the teams were about to use but possibility of concurrent access to users’ data didn’t appear to be a concern in their minds.

Wen you think about it, it seems to be very logical. People have a natural tendency to avoid problems they haven’t encountered directly. There are so few people with attitude of challenging obstacles not laying directly on their path. And after all, most of the questions managers tend to ask are like “when will you finish this use case”, “how much will this cost”. Eventually, the more technical aware PMs ask for iPad or recent web browser support. I’ve never heard a manager asking for example “what will happen when user A edits invoice while user B is issuing a warehouse document ?” question.

The fact that software tools providers does not mention concurrency handling (but not only that) in context of using their tools doesn’t help at all. There are still so many people who acquire their knowledge mostly from Microsoft marketing hype and don’t ask themselves these kind of tough questions. It is also very significant that Microsoft’s flag ORM product – Entity Framework – implements out of the box only very limited optimistic concurrency mechanism which is not enough for most of the real world systems.

To shed some light on the concurrency issue I decided to show a real live example of Offline Pessimistic Lock pattern I’ve implemented in my recent project.

The Offline Pessimistic Lock is very useful pattern. It allows you to ensure that, for example, only one user is altering an object’s data. In my example it happens by creating a separate lock object on the whole document. This object contains information about the type of locked object (i.e. domain entity), it’s key and a user which holds this lock.

The LockManager class is responsible for acquiring, releasing and ensuring these locks. The lock objects are stored in WriteLocks table.

CREATE TABLE [dbo].[WriteLocks](
	[OBJECT_TYPE] [nvarchar](255) NOT NULL,
	[OBJECT_ID] [nvarchar](50) NOT NULL,
	[ACCOUNT_ID] [int] NOT NULL,
	[ACQ_TIMESTAMP] [datetime] NOT NULL,
 CONSTRAINT [PK_WriteLocks] PRIMARY KEY CLUSTERED 
(
	[OBJECT_TYPE] ASC,
	[OBJECT_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
namespace ATMS.Core.Concurrency
{
    public class LockManager
    {
        /// <summary>
        /// Utility method for showing information about existing lock on object
        /// </summary>
        public void ShowNotification(Control parent)
        {
            MessageBox.Show(Res.Lock_ObjectBeingLocked, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }

        /// <summary>
        /// Ensures that lock on the object already exists
        /// </summary>
        /// <typeparam name="TObj">Entity type</typeparam>
        /// <param name="id">Entity key</param>
        /// <returns>Returns true if lock acquired by current user exists; false otherwise</returns>
        public bool EnsureLock<TObj>(object id)
        {
            var user = UserInfo.Current;

            if (user == null)
                throw new InvalidOperationException("User not logged in - can not release write lock");

            var typeStr = typeof(TObj).FullName;
            var idStr = id.ToString();

            using (var ctx = CoreEntitiesBuilder.Build())
            {
                var exist = ctx.WriteLocks.FirstOrDefault(x => x.OBJECT_TYPE == typeStr && x.OBJECT_ID == idStr && x.ACCOUNT_ID == user.AccountId);
                return exist != null;
            }
        }

        /// <summary>
        /// Removes lock acquired on the object hence it is available for locking by other users
        /// </summary>
        /// <typeparam name="TObj">Entity type</typeparam>
        /// <param name="id">Entity key</param>
        /// <returns>Returns true if there was a lock to release; false otherwise</returns>
        public bool ReleaseLock<TObj>(object id)
        {
            var user = UserInfo.Current;

            if (user == null)
                throw new InvalidOperationException("User not logged in - can not release write lock");

            var typeStr = typeof(TObj).FullName;
            var idStr = id.ToString();

            using (var ctx = CoreEntitiesBuilder.Build())
            {
                var exist = ctx.WriteLocks.FirstOrDefault(x => x.OBJECT_TYPE == typeStr && x.OBJECT_ID == idStr);

                if (exist == null)
                    return true;

                if (exist != null && exist.ACCOUNT_ID == user.AccountId)
                {
                    ctx.WriteLocks.Remove(exist);
                    ctx.SaveChanges();
                    return true;
                }
                else
                    return false;
            }
        }

        /// <summary>
        /// Removes a batch of locks acquired on objects
        /// </summary>
        /// <typeparam name="TObj">Entity type</typeparam>
        /// <param name="ids">Entity keys</param>
        /// <returns>Returns true if all locks were successfully released; false otherwise</returns>
        public bool ReleaseLocks<TObj>(object[] ids)
        {
            var user = UserInfo.Current;

            if (user == null)
                throw new InvalidOperationException("User not logged in - can not release write lock");

            var typeStr = typeof(TObj).FullName;
            var idStr = ids.Select(x => x.ToString());

            using (var ctx = CoreEntitiesBuilder.Build())
            {
                var exist = ctx.WriteLocks.Where(x => x.OBJECT_TYPE == typeStr && idStr.Contains(x.OBJECT_ID)).ToArray();

                var canRemove = exist.Where(x => x.ACCOUNT_ID == user.AccountId).ToArray();
                var canNotRemove = exist.Where(x => x.ACCOUNT_ID != user.AccountId).ToArray();

                ctx.WriteLocks.RemoveRange(canRemove);
                ctx.SaveChanges();

                return !canNotRemove.Any();
            }
        }

        /// <summary>
        /// Acquire locks on list of objects
        /// </summary>
        /// <typeparam name="TObj">Entity type</typeparam>
        /// <param name="ids">Entity keys</param>
        /// <returns>Returns true when all locks where successfully acquired; false otherwise</returns>
        public bool AcquireLocks<TObj>(object[] ids)
        {
            var user = UserInfo.Current;

            if (user == null)
                throw new InvalidOperationException("User not logged in - can not acquire write lock");

            var typeStr = typeof(TObj).FullName;
            var idStr = ids.Select(x => x.ToString());
            var result = false;

            using (var ctx = CoreEntitiesBuilder.Build())
            {
                var existing = ctx.WriteLocks.Where(x => x.OBJECT_TYPE == typeStr && idStr.Contains(x.OBJECT_ID)).ToArray();

                if (!existing.Any() ||
                    existing.All(x => x.ACCOUNT_ID == user.AccountId))
                {
                    foreach (var ex in existing)
                        ex.ACQ_TIMESTAMP = DateTime.Now;

                    var notExisting = idStr
                        .Where(x => !existing.Any(e => e.OBJECT_ID == x))
                        .Select(x => new WriteLocks()
                        {
                            ACCOUNT_ID = user.AccountId,
                            ACQ_TIMESTAMP = DateTime.Now,
                            OBJECT_ID = x,
                            OBJECT_TYPE = typeStr
                        });

                    try
                    {
                        ctx.WriteLocks.AddRange(notExisting);
                        ctx.SaveChanges();

                        result = true;
                    }
                    catch (SqlException)
                    {
                        result = false;
                    }
                }
                else
                    result = false;
            }

            return result;
        }

        /// <summary>
        /// Acquire lock on a single object
        /// </summary>
        /// <typeparam name="TObj">Entity type</typeparam>
        /// <param name="id">Entity key</param>
        /// <returns>Returns true when lock was acquired successfully; false otherwise</returns>
        public bool AcquireLock<TObj>(object id)
        {
            var user = UserInfo.Current;

            if (user == null)
                throw new InvalidOperationException("User not logged in - can not acquire write lock");

            var typeStr = typeof(TObj).FullName;
            var idStr = id.ToString();
            var result = false;

            using (var ctx = CoreEntitiesBuilder.Build())
            {
                var exist = ctx.WriteLocks.FirstOrDefault(x => x.OBJECT_TYPE == typeStr && x.OBJECT_ID == idStr);

                if (exist != null && exist.ACCOUNT_ID == user.AccountId)
                {
                    exist.ACQ_TIMESTAMP = DateTime.Now;
                    ctx.SaveChanges();

                    result = true;
                }
                else if (exist != null && exist.ACCOUNT_ID != user.AccountId)
                {
                    result = false;
                }
                else
                {
                    try
                    {
                        ctx.WriteLocks.Add(new WriteLocks()
                        {
                            ACCOUNT_ID = user.AccountId,
                            ACQ_TIMESTAMP = DateTime.Now,
                            OBJECT_ID = idStr,
                            OBJECT_TYPE = typeStr
                        });
                        ctx.SaveChanges();

                        result = true;
                    }
                    catch (SqlException)
                    {
                        result = false;
                    }
                }
            }

            return result;
        }
    }
}

Here are some examples of LockManager class usage:

private void btnSave_Click(object sender, EventArgs e)
{
	this.WithDbErrorHandling(
		() =>
		{
			if (!_lockMan.EnsureLock<rcr_CVDocument>(_currentDocument.ID))
			{
				_lockMan.ShowNotification(this);
				
				// ... 
			}
			else
				SaveDocumentData(_currentDocument.ID);
			
			// .... update UI controls etc.
		}
}

private void btnDelete_Click(object sender, EventArgs e)
{
	if (!_lockMan.AcquireLock<rcr_CVDocument>(_currentDocument.ID))
	{
		_lockMan.ShowNotification(this);
		return;
	}

	try
	{
		_currentDocument.TO_DELETE = true; //real delete handled by backend worker
		_entities.SaveChanges();
	}
	finally
	{
		_lockMan.ReleaseLock<rcr_CVDocument>(_currentDocument.ID);
	}
}

For further reading I suggest:

SoftProTech on AirFair 2015 Exposition

During the AirFair 2015 Expisition (an international event for military and aviation business) together with our business partner  – Military Aviation Works Facility – and TELDAT (award winning Polish defense market company) we have exhibited our company solutions – an Augmented Reality presentation of Military Aviation Works drone and a flight simulator designed for its pilots training.

Here are a few photos:

1 2 3 4