Sunday, February 18, 2018

karma-typescript-es6-transform: Fixing preset problem after upgrading

Recently, I upgraded the devDependencies in one of my projects to the current versions. This included an update of karma-typescript-es6-transformfrom version 1.0.2 to 1.0.3. Didn't sound dangorous. But afterwards my tests didn't run by karma any more. I just got:

18 02 2018 11:20:49.774:ERROR [karma]: Error: /Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/ionic-configuration-service/dist/index.js: Couldn't find preset "es2015" relative to directory "/Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/ionic-configuration-service/dist"
Transform function:

function (context, callback) {
        if (!context.js) {
            return callback(undefined, false);
        }
        if (isEs6(context.js.ast)) {
            options.filename = context.filename;
            log.debug("Transforming %s", options.filename);
            try {
                context.source = babel.transform(context.source, options).code;
                context.js.ast = acorn.parse(context.source, { sourceType: "module" });
                return callback(undefined, true);
            }
            catch (error) {
                return callback(error, false);
            }
        }
        else {
            return callback(undefined, false);
        }
    }

    at Transformer.handleError (/Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript/dist/bundler/transformer.js:108:19)
    at /Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript/dist/bundler/transformer.js:89:27
    at transform (/Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript-es6-transform/dist/transform.js:57:24)
    at /Users/markus/Development/Ritzlgrmft/ionic-logging-service/node_modules/karma-typescript/dist/bundler/transformer.js:83:17
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickCallback (internal/process/next_tick.js:104:9)

Raed more here: karma-typescript-es6-transform: Fixing preset problem after upgrading.

Saturday, December 2, 2017

Fixing RxJS import problems with Ionic's production build

Recently, I upgraded to Ionic 3.9.x, which included an upgrade to Angular 5.x as well. Everything went smooth, even I did not understand the RXJS stuff in Ionic's change log. But... when I did the first time a production build (ionic build --prod), I got a strange error:

Error: ./node_modules/rxjs/observable/BoundCallbackObservable.js Module build failed: TypeError: Cannot read property 'type' of undefined at Object.getEffectiveTypeAnnotationNode

Read more here: Fixing RxJS import problems with Ionic's production build.

Monday, May 1, 2017

Updating from Angular 2.4.8 to 4.0.0

Recently, I updated my component ionic-logging-service to the Angular version used by Ionic 3.0.0. After installing the new npm packages, I was able to build the component. But… tslint did no longer work. I got

.../ionic-logging-service/node_modules/tslint/lib/runner.js:92
    throw new Error(messages.join("\n"));
    ^

Error: Error at .../ionic-logging-service/node_modules/@angular/core/src/change_detection/differs/iterable_differs.d.ts:15:48: Cannot find name 'Iterable'.
  at Runner.run (.../ionic-logging-service/node_modules/tslint/lib/runner.js:92:27)
  at Object. (.../ionic-logging-service/node_modules/tslint/lib/tslint-cli.js:139:6)
  at Module._compile (module.js:409:26)
  at Object.Module._extensions..js (module.js:416:10)
  at Module.load (module.js:343:32)
  at Function.Module._load (module.js:300:12)
  at Module.require (module.js:353:17)
  at require (internal/module.js:12:17)
  at Object. (/Users/markus/Documents/Ritzlgrmft/ionic-logging-service/node_modules/tslint/bin/tslint:3:1)
  at Module._compile (module.js:409:26)
This happens due to Iterable, which is a es2015 feature. I solved it by adding the following to my tsconfig.json:

"compilerOptions": {
  "lib": [
    "dom",
    "es2015"
  ],
}

But now I got a lot of errors like

Error: Error at /Users/markus/Documents/Ritzlgrmft/ionic-logging-service/node_modules/@types/es6-shim/index.d.ts:6:14: Duplicate identifier 'PropertyKey’.

This happens, since es6-shim defines the same types as es2015 lib. Therefore I removed the package @types/es6-shim.

Now tslint was fine, again. But the karma tests failed. I got the error

Error: No provider for "framework:es6-shim"! (Resolving: framework:es6-shim)

This one was easy, since I uninstalled the package, I had to remove it from the frameworks. But now I got errors like

ERROR [compiler.karma-typescript]: node_modules/@angular/core/src/change_detection/differs/default_keyvalue_differ.d.ts(23,15): error TS2304: Cannot find name 'Map'. 
ERROR [compiler.karma-typescript]: node_modules/@angular/core/src/change_detection/differs/default_keyvalue_differ.d.ts(27,16): error TS2304: Cannot find name 'Map'.
ERROR [compiler.karma-typescript]: node_modules/@angular/core/src/change_detection/differs/iterable_differs.d.ts(15,48): error TS2304: Cannot find name 'Iterable'.

Again the problem are the es2015 features. Therefore I had to add the lib to the compilerOptions in karma.conf.js. However, this was not enough, since currently PhantomJS does not support the es2015 features. Therefore I had to add a polyfill to my files:

files: [
  { pattern:
      "src/**/*.+(ts|html)" },
      "node_modules/reflect-metadata/Reflect.js",
      "node_modules/babel-polyfill/dist/polyfill.js"
  }  
],

The used babel-polyfill was already there as a dependency of another package.

And finally, everything was working as before.

Saturday, March 25, 2017

karma-typescript: Invalid syntax in bundle: 'import' and 'export' may only appear at the top level

After upgrading some dependent modules, I got:
[Error: Invalid syntax in bundle: 'import' and 'export' may only appear at the top level (79532:186) in /var/folders/zk/6bvt6fls7g97ry69c8jykn_h0000gn/T/karma-typescript-bundle-62976QkhHVRO4DC9R.js]
Error: Invalid syntax in bundle: 'import' and 'export' may only appear at the top level (79532:186) in /var/folders/zk/6bvt6fls7g97ry69c8jykn_h0000gn/T/karma-typescript-bundle-62976QkhHVRO4DC9R.js
    at writeBundleFile (.../node_modules/karma-typescript/lib/bundler.js:171:23)
    at flushQueue (.../node_modules/karma-typescript/lib/bundler.js:99:9)
    at bundleQueuedModules (.../node_modules/karma-typescript/lib/bundler.js:94:9)
    at invokeFunc (.../node_modules/lodash.debounce/index.js:160:19)
    at trailingEdge (.../node_modules/lodash.debounce/index.js:207:14)
    at timerExpired [as _onTimeout] (.../node_modules/lodash.debounce/index.js:195:14)
    at Timer.listOnTimeout (timers.js:92:15)

The solution was to:
  • npm install karma-typescript@beta (I am using now 3.0.0-beta.2)
  • npm install karma-typescript-es6-transform@latest (I am using 1.0.0-beta.6)
  • add to karmaTypescriptConfig:
    bundlerOptions: {
      transforms: [
        require("karma-typescript-es6-transform")()
      ]
    }
    
For details see How to use es6 library.

Friday, March 24, 2017

Upgrading Angular dependencies to the versions used by Ionic 2.2

I provide a npm package, which contains a service thought for use in Ionic applications.
This service doesn't have any dependencies to Ionic itself, only to some Angular packages.
Since Ionic 2.2 uses newer versions of Angular (2.4.8 instead of 2.2.1), I updated the
Angular dependencies to the new versions. And then, ngc didn't work anymore. I got a strange error:
Error encountered resolving symbol values statically. Calling function 'NoOpAnimationDriver', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol AnimationDriver.NOOP in .../ionic-configuration-service/node_modules/@angular/platform-browser/src/dom/animation_driver.d.ts, resolving symbol BrowserTestingModule in .../ionic-configuration-service/node_modules/@angular/platform-browser/testing/browser.d.ts, resolving symbol BrowserTestingModule in .../ionic-configuration-service/node_modules/@angular/platform-browser/testing/browser.d.ts
The error message was not very helpful, also Google did not provide a lot of information.
I found only some posts from end of last year.

The solution was - finally - quite easy. I had to add a specific tsconfig.aot.json, which is used by ngc.
The differences to the original tsconfig.json are:
{
  "compilerOptions": {
    "module": "es2015"
  },
  "files": [
    "src/index.ts"
  ],
  "angularCompilerOptions": {
    "genDir": "aot"
  }
}
  • module es2015 is needed for tree shaking
  • files defines the entry point
  • genDir specifies where ngc stores its intermediate files
The last bit was to tell ngc to use the new configuration:
ngc -p tsconfig.aot.json
For a detailed explanation, just check the Angular Cookbook.

Tuesday, February 21, 2017

Ionic serve: watch failed: A watch configured to watch the following paths failed to start

Today I had the problem that every time, I called ionic serve, I got the following error:
...
[10:28:18]  build dev finished in 52.67 s
[10:28:22]  watch failed: A watch configured to watch the following paths failed to start. It likely that a file
            referenced does not exist:
            ...\src\**\*.(ts|html|s(c|a)ss)
[10:28:22]  dev server running: http://localhost:8100/
I found the error creation in node_modules\@ionic\app-scripts\dist\watch.js. The function startWatcher rejects the promise, if the start of watching needs more than 3 seconds. This is (roundabout) the timespan between "build dev finished" and "watch failed".

So I thought my machine might be too slow and increased the timeout value to 30 seconds:
[10:33:49]  build dev finished in 53.87 s
[10:34:09]  lint finished in 20.95 s
[10:34:19]  watch failed: A watch configured to watch the following paths failed to start. It likely that a file
            referenced does not exist: ...\src\assets\**\*,
            ...\src\index.html,
            ...\src\manifest.json,
            ...\src\service-worker.js,
            ...\node_modules\ionicons\dist\fonts\**\*,
            ...\node_modules\ionic-angular\fonts\**\*,
            ...\node_modules\ionic-angular\polyfills\polyfills.js,
            ...\node_modules\sw-toolbox\sw-toolbox.js,
            ...\environments\api-dwp\settings.json,
            ...\node_modules\leaflet\dist\leaflet.css
[10:34:19]  dev server running: http://localhost:8100/
As you can see, the error is the same, this time after 30 seconds, but there are more files reported. And indeed, the files ...\src\manifest.json and ...\src\service-worker.js do not exist. They are part of the default node_modules\@ionic\app-scripts\config\copy.config.js.

It seems, I have 2 possibilities now:
  • add the 2 files to my app
  • modify the copy.config.js

Since I have already a custom copy.config.js, and since I do not want to litter my source code with unneeded files, I used the second approach. In the copy.config.js, I had to override the key copyIndexContent (just remove '{{SRC}}/manifest.json' and '{{SRC}}/service-worker.js'):
copyIndexContent: {
  src: ['{{SRC}}/index.html'],
  dest: '{{WWW}}'
},
Now ionic serve didn't produce watch errors any more:
[11:19:40]  build dev finished in 50.56 s
[11:19:51]  watch ready in 62.29 s
[11:19:51]  dev server running: http://localhost:8100/
As you can see, the timespan for starting the watching is 11 seconds. So my modification of node_modules\@ionic\app-scripts\dist\watch.js is still needed.

Update 23-Feb-2017

Starting with version 1.1.4 of @ionic/app-scripts, there is a new option --startWatchTimeout to the ionic command. Instead of modifying the file node_modules\@ionic\app-scripts\dist\watch.js (which is not very reliable, anyway), it's enough to add the additional parameter: ionic serve --startWatchTimeout 30000.

See Issue 772.

Tuesday, November 1, 2016

Migrating to Ionic 2 RC 0 - ngc fails

Finally, I had my app migrated to Ionic 2 RC 0. I tested the app with ionic serve, I built and deployed the app with cordova run - everything was fine.
However, since the beginning of the process was ionic serve, this meant that from an Ionic point of view, everything happened in development mode. So I had no benefit from the performance gain by using Angular's AoT (Ahead-of-time compilation).

So I tried to do a real production build with ionic build. Unfortunately, it produced an error:
[12:51:11]  ionic-app-scripts 0.0.39
[12:51:11]  build prod started ... 
[12:51:11]  clean started ... 
[12:51:11]  clean finished in 7 ms 
[12:51:11]  copy started ... 
[12:51:11]  ngc started ... 
[12:51:11]  copy finished in 455 ms 
[12:51:12]  lint started ... 
[12:51:22]  lint finished in 9.64 s 
[12:51:32]  Error: Error at .../.tmp/app/app.module.ngfactory.ts:415:84 
[12:51:32]  Supplied parameters do not match any signature of call target. 
[12:51:32]  ngc failed 
[12:51:32]  ionic-app-script task: "build" 
[12:51:32]  Error: Error 
I had no idea, what was going wrong... Then I checked line 415 in app.module.ngFactory.ts (a generated file in the .tmp folder:
get _LoggingService_76():import46.LoggingService {
  if ((this.__LoggingService_76 == (null as any))) { (this.__LoggingService_76 = new import46.LoggingService()); }
    return this.__LoggingService_76;
}
Ok, it has something to do with my LoggingService, which I am using as provider in app.module.ts:
@NgModule({
  ...
  providers: [
    ConfigurationService
    LoggingService,
    ...
  ]
})
Then I checked the declaration of the LoggingService:
export declare class LoggingService {
  private configurationService;
  constructor(configurationService: ConfigurationService);
  ...
}
As you can see, its constructor has a parameter, but the code in app.module.ngFactory.ts is using a parameterless constructor.

One workaround could be to changed the parameter to an optional one. A better workaround is to change the declaration of the provider in app.module.ts:
@NgModule({
  ...
  providers: [
    ConfigurationService,
    {
      provide: LoggingService,
      useFactory: getLoggingService,
      deps: [ConfigurationService]
    },  
    ...
  ]
})
The last piece is the factory function:
export function getLoggingService(configurationService: ConfigurationService): LoggingService {
  return new LoggingService(configurationService);
}
That's it. Still a workaround, but working.

Tuesday, October 11, 2016

Ionic 2 - Using predefined colors

Today I wanted to style a list, in which one item could be selected. Such an selected item should have the primary color.

This meant mainly, I had to define a css class, which I assigned to a selected item:
.selected {
  background-color: #387ef5;
  color: #fff;
}
The background-color is the primary color. And as color, I chose something with a good contrast.

However, this approach is not very flexible. After all, Ionic provides SASS variables. So, why not use them?

Well, one reason could be, that the documentation is not complete. But finally, I made it:
  • The color values are defined in src/theme/variables.scss. You find there something like
    $colors: (
      primary:    #387ef5,
      secondary:  #32db64,
      danger:     #f53d3d,
      light:      #f4f4f4,
      dark:       #222,
      favorite:   #69BB7B
    );
  • For using a color value, Ionic provides a SCSS function named color() (defined in node_modules/ionic-angular/themes/ionic.functions.scss):
    .selected {
      background-color: color($colors, primary);
      color: #fff;
    }
  • For the text color, there is a similar function called color-contrast():
    .selected {
      background-color: color($colors, primary);
      color: color-contrast($colors, primary);
    }
  • If you want more control about the contrast color, you can define it by yourself in the color map, e.g.:
    $colors: (
      primary:    (base: #387ef5, contrast: #ff0000),
      ...
    );
    Now the text is red instead of white.

Sunday, October 9, 2016

Migrating to Ionic 2 RC 0 - CommonJS files

After migrating my app to Ionic 2 RC 0, I was able to typescript-compile my app, but the bundling failed with
bundle dev failed:  Module .../node_modules/log4javascript/log4javascript.js does not export getRootLogger 
Finally, after some googling, I found out, that I had to define the so-called custom named exports of log4javascript by myself:
  1. Copy rollup.config.js from node_modules/@ionic/app-scripts/config into my own config directory.
  2. Change my package.json according to the documentation:
    "config": {
      "ionic_rollup": "./config/rollup.config.js"
    },
    
  3. Add configuration for commonjs plugin in rollup.config.js:
    plugins: [
      commonjs({
        include: [
          'node_modules/log4javascript/**',
        ],
        namedExports: {
          'node_modules/log4javascript/log4javascript.js': [
            'Appender', 'BrowserConsoleAppender', 'getLogger', 'getRootLogger', 'Level', 'LoggingEvent', 'logLog', 'PatternLayout'
          ]
        }
      }),
      ...
    ]
    
As you can see, I had to define every single thing I used from log4javascript. Not beautiful, but it works.

Tuesday, October 4, 2016

Migrating to Ionic 2 RC 0 - Custom Config Files

I followed the documentation about Custom Config Files. For this, I copied the rollup.config.js from node_modules/@ionic/app-scripts/config into my own config directory. Then I changed my package.json according to the documentation:
"config": {
  "ionic_rollup": "./config/rollup.config.js"
},
But now the build reported:
Config file "/Users/.../config/rollup.config.js" not found. Using defaults instead.
Error: Cannot find module '../dist/plugins/ng-template'
As every developer, I tried to solve the first message. But the config file was at exact the reported place. Finally, after checking the code, I found it just "misleading". The problem ist the second message. The first line of the copied rollup.config.js was
var ngTemplate = require('../dist/plugins/ng-template').ngTemplate;
Since I moved the file, the relative path didn't work any more. There for I changed the first line to
var ngTemplate = require('../node_modules/@ionic/app-scripts/dist/plugins/ng-template').ngTemplate;
And both error messages were history.

Update 9-Oct-2016: Latest with @ionic/app-scripts@0.0.30, ngTemplate is no longer required by rollup. Therefore the mentioned error does not occur anymore.

Wednesday, March 30, 2016

OWIN on IIS7

I have a long journey behind me with trying to deploy my ASP.NET application using OWIN on IIS7. But finally, I was successful.

Most articles mention:
  • Ensure that Microsoft.Owin.Host.SystemWeb.dll is in the bin folder of the web application
  • Run app pool in V4.0 integrated mode
  • Add runAllManagedModulesForAllRequests to your Web.config:
    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true" />       
    </system.webServer>
Unfortunately, this was not enough for me. The final hint I found in the Katana Wiki. I had to add
app.UseStageMarker(PipelineStage.MapHandler);
to the end of my Startup.Configuration() method. Really important is “the end”.

Some details, you can find in OWIN Middleware in the IIS integrated pipeline. The article describes how the pipelines in OWIN and ASP.NET relate. Very helpful for me was the tracing of the current pipeline stage.

My code was something like
public void Configuration(IAppBuilder app)
{
  app.UseStaticFiles();
  app.MapSignalR();
  app.UseWebApi();
}
With the tracing, I saw the following the following stages on IIS7:
  • UseStaticFiles: AuthorizeRequest
  • MapSignalR: AuthorizeRequest
  • UseWebApi: missing
On IIS8.5, the behavior was slightly different:
  • UseStaticFiles: AuthorizeRequest
  • MapSignalR: AuthorizeRequest
  • UseWebApi: PreExecuteRequestHandler
On IIS8.5, everything was working, but on IIS7, WebApi couldn’t work, since it was not reached in the pipeline.

My first suspicion was, that MapSignalR did prevent the further processing. But this was completely wrong. Due to MapSignalR at least everything before (including SignalR) worked, since MapSignalR is setting the stage marker PostAuthorize.

Unfortunately everything later than MapHandler is not executed on IIS7. Therefore I had to add the appropriate call at the end of my method:
public void Configuration(IAppBuilder app)
{
  app.UseStaticFiles();
  app.MapSignalR();
  app.UseWebApi();
  app.UseStageMarker(PipelineStage.MapHandler);
}
Now everything is working latest in stage MapRequestHandler, in my case:
  • UseStaticFiles: AuthorizeRequest
  • MapSignalR: AuthorizeRequest
  • UseWebApi: MapRequestHandler
Unfortunately, I still do not know, why this is necessary. But at least I have a working solution now.

Tuesday, March 29, 2016

Do not use Microsoft.AspNet.SignalR.Owin any longer

Today I had some strange warnings in one of my web projects using SignalR and Owin. After longer probing and testing, also asking at Stack Overflow, I finally found the reason.

I used the following NuGet packages (beside others):

Both components contain some classes in the same namespace (e.g. Microsoft.AspNet.SignalR.WebSockets.DefaultWebSocketHandler). Unfortunately, not all implementations (worse: not all signatures) are identical. This caused my problems.

The solution is simple: just do not use the NuGet package Microsoft.AspNet.SignalR.Owin any longer! Microsoft.AspNet.SignalR.Core is enough.

Owin Default Files

I just came around the problem that I wanted to serve static files via Owin. Therefore I added the NuGet package Microsoft.Owin.StaticFiles, and added the following to my Startup class:

app.UseStaticFiles();
app.UseDefaultFiles(new DefaultFilesOptions 
  { 
    DefaultFileNames = new[] { "index.html" } 
  });

With this, I was able to serve static files, but the default document didn’t work. I always got a HTTP 404 response. Even though I saw the access to the file in the Process Monitor. Finally, I found the solution in one of the answers to a question of Stack Overflow. I had to call UseDefaultFiles first! Before UseStaticFiles:

app.UseDefaultFiles(new DefaultFilesOptions 
  { 
    DefaultFileNames = new[] { "index.html" } 
  });
app.UseStaticFiles();

Sunday, March 20, 2016

CodeAnalysis broken on TFS build

Today I created a new build on TFS. The compile step was successful, but not the code analysis. It failed with

(RunCodeAnalysis target) ->
  MSBUILD : error : CA0001 : The following error was encountered while reading module '...': Could not resolve member reference: [System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]System.Net.Http.Formatting.BaseJsonMediaTypeFormatter::get_SerializerSettings.

This was quite confusing, since I had referenced the correct version. Since I had the same problem already 4 weeks ago, but couldn’t remember it today, I decided to write this post.

In the detailed build output I found also

Unified primary reference "Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed".
Using this version instead of original version "6.0.0.0" in "...\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll" because AutoUnify is 'true'.

System.Net.Http.Formatting is referencing Newtonsoft.Json in version 6.0.0.0, but I referenced it in version 8.0.0.0. This doesn’t make problems with the build, and also at runtime, there are no problems (due to the assemblyBinding). But code analysis cannot handle it out of the box.

The solution is to enhance the FxCop command with /assemblyCompareMode:StrongNameIgnoringVersion. I did this by adding a property to my .csproj file:

<propertygroup>
  <codeanalysisadditionaloptions>/assemblyCompareMode:StrongNameIgnoringVersion</codeanalysisadditionaloptions>
</propertygroup>

That’s it!

Sunday, October 26, 2014

Problem with id="global" in HTML

I am a fan of Moment.js. I am using is quite often for formatting dates in my Knockout view models.

In my current project I made some changes, and suddenly, it didn’t work any longer. I only got the exception

  • TypeError: Object expected (Internet Explorer)
  • ReferenceError: moment is not defined (Chrome & Firefox)


I ended up with deleting nearly everything in my page - until it worked again. So I found out the reason (after hours): I had added the following element:

<div id="global"> ... </div>

And this confused Moment.js. Because Moment.js contains support for Node.js:

var moment,
VERSION = '2.8.2',
// the global-scope this is NOT the global object in Node.js
globalScope = typeof global !== 'undefined' ? global : this,
...

That means with my div, globalScope will be set to this div, which doesn’t provide the expected functionality. So my lesson is to never use the id “global” again in my HTML code – at least as long as I can remember this problem.

I hope this post helps to remember.

Sunday, March 9, 2014

Corrupt user.config file

Sometimes – fortunately very seldom – I have the problem that the user.config file is corrupt. When it happens, I get something like

System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize 
---> System.Configuration.ConfigurationErrorsException: Root element is missing. (C:\Users\_user_\AppData\Local\_appdomain_evidenceType_evidenceHash_\_version_\user.config) 
---> System.Xml.XmlException: Root element is missing.

The problem is that in this case I cannot access any setting. No userSettings and also no applicationSettings.

The recommended solution is to delete the user.config file in this case. This is easy since the exception contains the complete file name. And the user has also the permission to delete the file.

However, I didn’t want to bother the user. I thought my program could do the same stuff by itself. Therefore I added the following coding at the beginning of my program (before the first setting will be accessed):

bool isConfigurationValid = false;
while (!isConfigurationValid)
{
  try
  { 
    // access one arbitrary setting
    var x = Settings.Default.Dummy;
    // leave while loop
    isConfigurationValid = true;
  }
  catch (ConfigurationErrorsException e)
  {
    ConfigurationErrorsException innerException = e.InnerException as ConfigurationErrorsException;
    if (innerException != null && innerException.Filename.EndsWith("user.config"))
    {
      File.Delete(innerException.Filename);
      Settings.Default.Reload();
    }
    else
      // other exception; will be not handled here
      throw;
  }
}
The idea was to catch an eventual ConfigurationErrorsException and to delete the corrupt user.config file. So far it worked. But after deleting the file I wanted to reload the settings. And this did not work. The corrupt user.config file remained cached. I also replaced the call of Reload() with Reset() or Upgrade(), but the result remained the same.

The solution was to load the settings explicitly (not via Settings.Default). Instead I used ConfigurationManager.OpenExeConfiguration(). Also this method throws an error when one user.config file is corrupt. But it has no influence on Settings.Default:

bool isConfigurationValid = false;
while (!isConfigurationValid)
{
  try
  {
    AppSettingsSection appSettings = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).AppSettings;
    isConfigurationValid = true;
  }
  catch (ConfigurationErrorsException e)
  {
    if (e.Filename.EndsWith("user.config"))
      File.Delete(e.Filename);
  }
}

This coding has the additional advantage that I do not have to handle the nested ConfigurationErrorsExceptions. It is only important to call it before the first access to Settings.Default.

Saturday, March 8, 2014

Working with SqlAzureExecutionStrategy

One of my favorite features of Entity Framework 6 was the SqlAzureExecutionStrategy. At least I thought this. I wanted to use it also in my company’s network since we get periodically timeouts connecting to a SQL Server instance (SQL Server 2008 in this case). So I thought, perfect, the SqlAzureExecutionStrategy is the solution to my problem. However, it didn’t work. So I had to investigate...

My approach was to write a sample program starting two tasks:
  • Task 1 should open a transaction, update a database row and then wait for some time (without committing or rolling back the transaction)
  • Task 2 should try to modify the same database row during Task 1’s wait time
Without any further preparation, this approach raised an exception in Task 2:
System.Data.SqlClient.SqlException (0x80131904): Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out
Next I added my own implementation of DbConfiguration, which simply configured the execution strategy:
public class MyDbConfiguration : DbConfiguration
{
  public MyDbConfiguration()
  {
    this.SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy()); 
  }
}
Unfortunately, this approach did not work since the "retrying execution strategies" do not support user-initiated transactions (see Limitations with Retrying Execution Strategies (EF6 onwards)). To my rescue, the mentioned article describes also a workaround which prevents the usage of the SqlAzureExecutionStrategy together with the transaction:
public class MyDbConfiguration : DbConfiguration
{
  public MyDbConfiguration()
  {
    this.SetExecutionStrategy("System.Data.SqlClient", () => SuspendExecutionStrategy
      ? (IDbExecutionStrategy)new DefaultExecutionStrategy()
      : new SqlAzureExecutionStrategy()); 
  }
  public static bool SuspendExecutionStrategy
  {
    get { return (bool?)CallContext.LogicalGetData("SuspendExecutionStrategy") ?? false; }
    set { CallContext.LogicalSetData("SuspendExecutionStrategy", value); }
  }
}
Now the program was running again, but I still got the SqlException with the timeout. Therefore I now added some database logging, another great feature of Entity Framework 6 (see Logging and Intercepting Database Operations). The log was interesting, since it provided some additional insights. But with my special problem, it was not really useful.
10:49:56,117 Task 1: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:49:56,119 Task 1: -- @0: '3/5/2014 10:49:56 AM' (Type = DateTime2)
10:49:56,119 Task 1: -- @1: '3621840d-724e-4a62-b22a-accb215dfb1b' (Type = Guid)
10:49:56,120 Task 1: -- Executing at 3/5/2014 10:49:56 AM +01:00
10:49:56,130 Task 1: -- Completed in 6 ms with result: 1
10:49:56,430 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:49:56,431 Task 2: -- @0: '3/5/2014 10:49:56 AM' (Type = DateTime2)
10:49:56,431 Task 2: -- @1: '3621840d-724e-4a62-b22a-accb215dfb1b' (Type = Guid)
10:49:56,431 Task 2: -- Executing at 3/5/2014 10:49:56 AM +01:00
10:50:01,557 Task 2: -- Failed in 5124 ms with error: Timeout expired.  The timeout period elapsed pr...
10:50:01,732 Task 2: update failed
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
The statement has been terminated. ---> System.ComponentModel.Win32Exception: The wait operation timed out
Then I had a look at the source code of SqlAzureExecutionStrategy. Its implementation consists of mainly one method:
protected override bool ShouldRetryOn(Exception exception)
{
  return SqlAzureRetriableExceptionDetector.ShouldRetryOn(exception);
}
Since the method is protected, I decided to implement my own strategy derived from SqlAzureExecutionStrategy. In the first step, I simply implemented my own ShouldRetryOn method, which I used for setting a breakpoint. I found out that the method was called with the SqlException, but SqlAzureExecutionStrategy's implementation returned false.

SqlAzureExecutionStrategy delegates the check of the exception to SqlAzureRetriableExceptionDetector. As you can see in the source code, it returns true for TimeoutException and for SqlException with a specific SqlError. However, "my" SqlException has a SqlError with Number == -2, for which false is returned.

Now I added some real logic to my strategy:
protected override bool ShouldRetryOn(Exception exception)
{
  bool shouldRetry = false;

  SqlException sqlException = exception as SqlException;
  if (sqlException != null)
  {
    foreach (SqlError error in sqlException.Errors)
    {
      if (error.Number == -2)
        shouldRetry = true;
    }
  }

  shouldRetry = shouldRetry || base.ShouldRetryOn(exception);

  Logger.WriteLog("ShouldRetryOn: " + shouldRetry);
  return shouldRetry;
}
With this implementation I had two benefits:
  • The connection attempt was retried also in my deadlock scenario.
  • I got a log entry for every retry.
Now I got a "beautiful" trace of my retry activities. And also an explicit exception, when the problem couldn’t be solved by simply retrying it:
10:56:15,805 Task 1: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:56:15,807 Task 1: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:56:15,808 Task 1: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:56:15,808 Task 1: -- Executing at 3/5/2014 10:56:15 AM +01:00
10:56:15,816 Task 1: -- Completed in 5 ms with result: 1
10:56:15,823 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:56:15,824 Task 2: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:56:15,824 Task 2: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:56:15,824 Task 2: -- Executing at 3/5/2014 10:56:15 AM +01:00
10:56:20,949 Task 2: -- Failed in 5123 ms with error: Timeout expired.  The timeout period elapsed pr...
10:56:21,046 MyExecutionStrategy: retrying
10:56:21,051 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:56:21,051 Task 2: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:56:21,052 Task 2: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:56:21,052 Task 2: -- Executing at 3/5/2014 10:56:21 AM +01:00
10:56:26,137 Task 2: -- Failed in 5083 ms with error: Timeout expired.  The timeout period elapsed pr...
10:56:26,219 MyExecutionStrategy: retrying
10:56:27,241 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:56:27,241 Task 2: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:56:27,242 Task 2: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:56:27,242 Task 2: -- Executing at 3/5/2014 10:56:27 AM +01:00
10:56:32,323 Task 2: -- Failed in 5080 ms with error: Timeout expired.  The timeout period elapsed pr...
10:56:32,404 MyExecutionStrategy: retrying
10:56:35,653 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:56:35,653 Task 2: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:56:35,653 Task 2: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:56:35,654 Task 2: -- Executing at 3/5/2014 10:56:35 AM +01:00
10:56:40,737 Task 2: -- Failed in 5083 ms with error: Timeout expired.  The timeout period elapsed pr...
10:56:40,822 MyExecutionStrategy: retrying
10:56:47,901 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:56:47,901 Task 2: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:56:47,901 Task 2: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:56:47,902 Task 2: -- Executing at 3/5/2014 10:56:47 AM +01:00
10:56:52,982 Task 2: -- Failed in 5080 ms with error: Timeout expired.  The timeout period elapsed pr...
10:56:53,066 MyExecutionStrategy: retrying
10:57:08,527 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
10:57:08,529 Task 2: -- @0: '3/5/2014 10:56:15 AM' (Type = DateTime2)
10:57:08,530 Task 2: -- @1: '4e3554be-1e13-461b-af12-848575317beb' (Type = Guid)
10:57:08,531 Task 2: -- Executing at 3/5/2014 10:57:08 AM +01:00
10:57:13,615 Task 2: -- Failed in 5082 ms with error: Timeout expired.  The timeout period elapsed pr...
10:57:13,699 MyExecutionStrategy: retrying
10:57:13,745 Task 2: update failed
System.Data.Entity.Infrastructure.RetryLimitExceededException: Maximum number of retries (5) exceeded while executing database operations with 'MyExecutionStrategy'. See inner exception for the most recent failure. ---> System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Timeout expired.  The timeout period elapsed prior to completion of the operation or the server
is not responding.
The statement has been terminated. ---> System.ComponentModel.Win32Exception: The wait operation timed out
As you can see, the System.Data.Entity.Infrastructure.DbUpdateException was changed now into a System.Data.Entity.Infrastructure.RetryLimitExceededException. The inner System.Data.Entity.Core.UpdateException remains the same.

My final issue was that I did misunderstand the optional parameters of SqlAzureExecutionStrategy: maxRetryCount is simply the maximum number of retries. But with maxDelay it is more complicated. The delay between the retries is connected to retry number and the power of 2. This results in the following delay intervals (ignoring some minor random stuff):
0, 1, 3, 7, 15, 31, 63, ... (seconds)
You can see the delay also in the trace above: the timespan between "MyExecutionStrategy: retrying" and "Task 2: UPDATE ...".

maxDelay does not set the duration of the complete operation (from first try until last retry). This was my expectation. Instead it limits only the delay between two retries. With a maxDelay of 5, we get par example:
0, 1, 3, 5, 5, 5, 5, ...

In the last trace, you can see the decreased maxDelay. And also the final success, since here Task 1 rolls back after 50 seconds:
11:06:39,953 Task 1: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:06:39,955 Task 1: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:06:39,955 Task 1: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:06:39,955 Task 1: -- Executing at 3/5/2014 11:06:39 AM +01:00
11:06:39,964 Task 1: -- Completed in 4 ms with result: 1
11:06:39,971 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:06:39,971 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:06:39,972 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:06:39,972 Task 2: -- Executing at 3/5/2014 11:06:39 AM +01:00
11:06:45,097 Task 2: -- Failed in 5123 ms with error: Timeout expired.  The timeout period elapsed pr...
11:06:45,193 MyExecutionStrategy: retrying
11:06:45,199 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:06:45,199 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:06:45,199 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:06:45,200 Task 2: -- Executing at 3/5/2014 11:06:45 AM +01:00
11:06:50,283 Task 2: -- Failed in 5082 ms with error: Timeout expired.  The timeout period elapsed pr...
11:06:50,368 MyExecutionStrategy: retrying
11:06:51,427 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:06:51,427 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:06:51,427 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:06:51,428 Task 2: -- Executing at 3/5/2014 11:06:51 AM +01:00
11:06:56,510 Task 2: -- Failed in 5080 ms with error: Timeout expired.  The timeout period elapsed pr...
11:06:56,593 MyExecutionStrategy: retrying
11:06:59,686 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:06:59,687 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:06:59,687 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:06:59,687 Task 2: -- Executing at 3/5/2014 11:06:59 AM +01:00
11:07:04,772 Task 2: -- Failed in 5083 ms with error: Timeout expired.  The timeout period elapsed pr...
11:07:04,853 MyExecutionStrategy: retrying
11:07:09,858 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:07:09,858 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:07:09,859 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:07:09,859 Task 2: -- Executing at 3/5/2014 11:07:09 AM +01:00
11:07:14,939 Task 2: -- Failed in 5079 ms with error: Timeout expired.  The timeout period elapsed pr...
11:07:15,023 MyExecutionStrategy: retrying
11:07:20,028 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:07:20,029 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:07:20,030 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:07:20,031 Task 2: -- Executing at 3/5/2014 11:07:20 AM +01:00
11:07:25,113 Task 2: -- Failed in 5080 ms with error: Timeout expired.  The timeout period elapsed pr...
11:07:25,196 MyExecutionStrategy: retrying
11:07:29,968 Task 1: rolling back
11:07:29,975 Task 1: rolled back
11:07:30,201 Task 2: UPDATE [dbo].[T_Message] SET [LastUpdate] = @0 WHERE ([Messageid] = @1)
11:07:30,202 Task 2: -- @0: '3/5/2014 11:06:39 AM' (Type = DateTime2)
11:07:30,204 Task 2: -- @1: 'c5da0be6-a4a8-4018-9c8f-c1062aa9a958' (Type = Guid)
11:07:30,204 Task 2: -- Executing at 3/5/2014 11:07:30 AM +01:00
11:07:30,208 Task 2: -- Completed in 2 ms with result: 1
FInally, I was even more enthusiatstic with SqlAzureExecutionStrategy than before. And I hope, you are, too.

Sunday, November 10, 2013

log4javascript and ASP.NET Web Api

log4javascript is a nice logging framework for JavaScript. With it you can log to the browser console (if supported by the browser), but also to an own window and even to the server via AJAX calls. For the latter, you need also something on the server which can handle the AJAX requests. Here I wanted to use ASP.NET Web Api. Since I didn’t find any documentation on this specific topic, I want to share my experiences here.

In general, the whole stuff is quite easy. On the client side you have to define the AjaxAppender:

var ajaxAppender = new log4javascript.AjaxAppender(serverUrl);
ajaxAppender.setLayout(new log4javascript.JsonLayout());
ajaxAppender.addHeader("Content-Type", "application/json; charset=utf-8");
I thought, with Web Api JSON would be the most natural data format. The more tricky line is the last one. Without it, the Content-Type header has the value application/x-www-form-urlencoded. This causes Web Api to use the JQueryMvcFormUrlEncodedFormatter. Unfortunately, this formatter cannot handle the JSON formatted data.
After specifying the correct content type, Web Api uses the JsonMediaTypeFormatter. And everything is fine.

On the server side, I first had to define the structure of the log data:

public struct LogEntry
{
  public string Logger;
  public long Timestamp;
  public string Level;
  public string Url;
  public string Message;
}
Since log4javascript can send more than one log entry in one AJAX call, my logging method gets an array of LogEntry instances. Additionally I needed to convert the timestamp value, since log4javascript sends it in milliseconds since 01-Jan-1970:
public void Write(LogEntry[] data)
{
  if (data != null)
  {
    foreach (LogEntry entry in data)
    {
      DateTime timestampUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(entry.Timestamp);
      DateTime timestampLocal = timestampUtc.ToLocalTime();
      ...
    }
  }
}
That’s it!

Sunday, September 22, 2013

Registration-Free COM with ActiveX Controls

In my current project, I have beside other things, a form with an ActiveX control on it. Additionally, I am using registration free COM, meaning that the COM information is stored in a manifest file instead of the registry.

Everything worked fine, until I tried to create the form a second time. Also this worked without problems, but not in the Visual Studio debugger. Here I got the strange exception:

System.NotSupportedException: Unable to get the window handle for the 'xxx' control. Windowless ActiveX controls are not supported.
at System.Windows.Forms.AxHost.EnsureWindowPresent()
at System.Windows.Forms.AxHost.InPlaceActivate()
at System.Windows.Forms.AxHost.TransitionUpTo(Int32 state)
at System.Windows.Forms.AxHost.CreateHandle()
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.AxHost.EndInit()

After hours of thinking, debugging, code stripping and so on (to be honest, mainly from a colleague of me), we found the solution: the used manifest was not complete. The manifest was created using mt.exe with the typelib of the control. This manifest looked like

<file name="..." hashalg="SHA1">
  <comClass clsid="..." tlbid="..." description="..." />
  <typelib tlbid="..." version="..." helpdir="" />
</file>

When we compared this with the entries in the registry, we saw in the registry much more things. And also in the assembly manifest documentation are more attributes mentioned. Therefore we tried to add as much attributes to the manifest as possible (even if we did not understand every bit completely). And viola, now the error was gone!

In total, we added 4 attributes (in our case):

<file name="..." hashalg="SHA1">
  <comClass clsid="..." tlbid="..." description="..." threadingModel="..." progid="..." miscStatus="..." />
  <typelib tlbid="..." version="..." helpdir="" flags="..." />
</file>

I hope this could help, if you have a similar issue.

Wednesday, September 18, 2013

AppDomains and user.config

Previously I had a problem with an application using different AppDomains. In one AppDomain I wrote some settings to an user.config file. And then I got in another AppDomain the following exception:
System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize
---> System.Configuration.ConfigurationErrorsException: Unrecognized configuration section userSettings. (C:\Users\uuuuuuuu\AppData\Local\cccccc\aaaaaaaaaaaaaa_Url_4v0elz3yo0gytsdhg5vusobffefqs0so\1.0.0.0\user.config line 3)


This was very strange since in this AppDomain I even didn’t use any user.config. After some hours of investigation, I found the problem. The user.config file will be written to

<Profile Directory>\<Company Name>\<App Domain>_<Evidence Type>_<Evidence Hash>\<Version>\user.config
  • <Profile Directory>: %APPDATA% or %LOCALAPPDATA%
  • <Company Name>: value of AssemblyCompanyAttribute, trimmed to 25 characters, invalid characters replaced by '_'
  • <App Domain>: friendly name of the current AppDomain, trimmed to 25 characters, invalid characters replaced by '_'
  • <Evidence Type> and <Evidence Hash>: some magic from AppDomain’s evidence
  • <Version>: value of AssemblyVersionAttribute
In my case, all these properties had the same value. Therefore the whole stuff got mixed up: 
  • company name: ok this is by purpose the same
  • version: I generated the version from the build number for all assemblies, but also this is not so uncommon
  • evidence: I created the new AppDomains with
    AppDomain.CreateDomain(appDomainName, null, appDomainSetup);
    The second parameter is the evidence for the new AppDomain. If it is null, the evidence from the current AppDomain will be taken. Also understandable.
  • App domain: this was the sticky point. I used for the AppDomain’s name the full name of the main assembly. Since I used a pattern like Company.Application.Subsystem..., this name was simply too long. The first 25 characters were all the same...
So the solution was quite easy: I just had to change the AppDomain’s name. But the trail to the solution took its time. Maybe this blog can accelerate your search a little bit.

BTW: Finally I checked the source code of System.Configuration.ClientConfigPaths.cs. This helped me a lot to understand the problem.