Wednesday, 21 February 2018

The woes of setting up and deploying TX Text control

Recently I've had to set up and configure TX TextControl, and I found this to be quite a bit more challenging than I expected it to be. Mainly this was because of the serious lack of quality documentation. That's not to say that they don't have docs, it's just that it's... confusing and not very well structured. It took me some time and a number of trials and errors to finally have it set up and running on the server. I thought it might be useful for me to share my experience with this, and provide a short and simple summary of the configuration I ended up with.

In our case we were deploying it to a asp.net mvc web application, and we were using two ways to access it - we needed both the web based editor and we the ability to generate documents by an automated process. Which apparently are very separate parts of the app. We also use a build server for automated CI/CD, which brought some fun to the process!

Initial setup

Adding TX TextControl to a web app is actually quite simple. You have download and run the installer from their website (in our case we needed the TX TextControl Server for ASP.NET), and add the TXTextControl.Web NuGet package to your project! Simple! The NuGet package brings a LOT of files with it! Over 450 css, image, js, cursor, html template files and so on.

Immediately after that we hit our first hurdle - our app didn't have the WebAPI as part of our web project, so the editor wouldn't load. Even if you did, the app assumes that your web API controllers are available under the default /api route. Otherwise you just get an error message when trying to load the page with the editor:

Can't connect to the server. Please refresh the website to re-connect

The second part of the app was supposed to generate documents by an automated process. To do this, we'd have to use the TXTextControl.ServerTextControl class. For this to work, we have to add our license to the app, otherwise the component won't work. This should be as simple as following the instructions on their .NET Licensing Explained page, and involves creating a licenses.licx file. Don't forget that this has to be added to the calling assembly, not the library project that will be instantiating the class.

Building the project

This is where it got a bit fun. The project will build and work just fine on the developer's machine who has the TextControl server app installed. But any other developer's environment, or the build server won't be able to build the project. The main reason for that was the licenses.licx file. The build process will see the file, and try to embed license information in to your assembly, and unless you have the TX TextControl installed on this machine, the build process will fail with the following error message:

licenses.licx(1, 0): error LC0004: Exception occurred creating type 'TXTextControl.ServerTextControl, TXTextControl.Server, Version=25.0.1000.500, Culture=neutral, PublicKeyToken=6b83fe9a75cfb638' System.IO.FileNotFoundException: Could not load file or assembly 'txkernel, Version=25.0.2500.500, Culture=neutral, PublicKeyToken=6b83fe9a75cfb638' or one of its dependencies. The system cannot find the file specified.

After some digging around, I found this post on their blog explaining how to make this work with a build server. What you have to do is run lc.exe to generate the embeddable license information, and add it as an embedded resource to your assembly, replacing licenses.licx (don't forget to delete it!). After following the instructions, the build server managed to build the app, but the code would fail to work - it was as if there was no license information available at all:

[System.ComponentModel.LicenseException: The following control could not be licensed: TXTextControl.ServerTextControl]
   at ᜁ.᜿.GetLicense(LicenseContext context, Type type, Object instance, Boolean allowExceptions)
   at System.ComponentModel.LicenseManager.ValidateInternalRecursive(LicenseContext context, Type type, Object instance, Boolean allowExceptions, License& license, String& licenseKey)
   at System.ComponentModel.LicenseManager.Validate(Type type, Object instance)
   at TXTextControl.ServerTextControl..ctor(Type controlType)

There were many times during the integration when I wanted to bring up my decompiler to help me out, and while it wasn't as useful with this library (the code is HEAVILY obfuscated, just look at the unicode method names above :) ), it pointed me in the right direction - by default embedded resources will be named as follows: {default.project.namespace}.{file_path}.{file_name}. With the file being called AssemblyName.dll.licenses, the resource name ended up being Namespace.AssemblyName.dll.licenses. A quick dive into the .csproj file and I added the following, overriding the resource name:

<EmbeddedResource Include="AssemblyName.dll.licenses"> 
  <LogicalName>AssemblyName.dll.licenses</LogicalName> 
</EmbeddedResource> 

Deploying to the web server

You'd think that was it, right? Nope. Once deployed to your web server, neither the web editor, nor the ServerTextControl class would work. The web editor was back to step one where it failed to connect to the server. Digging into this I learned that the actual editor backend isn't hosted as part of your web app, but instead it's handled by a windows service running on your server. So to deploy the app, you would need to install said service on your server.

You could do that by downloading and installing the server exe mentioned at the top of this post, but since we were running headless servers, I checked if I had any alternatives. Digging through their documentation, and a number of cross referencing pages, semi duplicated posts and going in the wrong direction a couple times, I finally found this post: Redistribution and Deployment. Ultimately, what you need to do is copy a number of files from your dev machine to a new folder on your server (I went with C:\TXTextControl), and run the following command to register it on your server:

txregwebsvr.exe /i /e /w

Make sure your web server has web socket support enabled in IIS, since TextControl depends on it! Once this is done, the web editor started loading!

Additional libraries

The TXTextControl.ServerTextControl class would fail to process documents though, throwing the following exception:

[System.IO.FileNotFoundException: Could not load file or assembly 'txkernel, Version=25.0.2500.500, Culture=neutral, PublicKeyToken=6b83fe9a75cfb638' or one of its dependencies. The system cannot find the file specified.]
   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
   at ᜁ.᜶.ᜀ(AssemblyName )
   at ᜁ.᜶.ᜀ(String , String , String , Assembly& )
   at ᜁ.᜷..ctor()
   at TXTextControl.ServerTextControl..ctor(Type controlType)

Once I copied the file in, I got an even more exciting exception!

[System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.]
   at System.Runtime.InteropServices.Marshal.ReadInt16(IntPtr ptr, Int32 ofs)
   at TXTextControl.KernelHelper.Ptr2StringArray(IntPtr ptr, Int32 iStrings)
   at TXTextControl.LoadSettings.ᜀ(ᝊ , TextControlCore )
   at TXTextControl.LoadSettings.ᜀ(Byte[] , BinaryStreamType , TextControlCore , ᜩ )
   at TXTextControl.ServerTextControl.Load(Byte[] binaryData, BinaryStreamType binaryStreamType, LoadSettings loadSettings)
   at TXTextControl.DocumentServer.MailMerge.LoadTemplateFromMemory(Object template, FileFormat documentFormat)

This would also occasionally crash the whole web app, and throw up a popup on the server. Oh dear! That doesn't look good at ALL. What I realised is that I have to copy most of the same files that were needed for the deployment above (.dll libraries from the installation's Assembly and bin64 paths) to the bin path of my app (sans the .exe files). Once I copied them in and restarted the web app for good measure, I could run the app. I went back to my code, and copied these files to the bin path during the build process. Unfortunately, the build server ended up coughing with the following build error:

ASPNETCOMPILER error ASPCONFIG: Could not load file or assembly 'txic' or one of its dependencies. An attempt was made to load a program with an incorrect format.

So the standard build wouldn't fail, but then attempting to build the views, it didn't like the extra libraries. What I ended up doing is keeping these dlls in a separate path, and copying them into the bin path after the build step but before the deployment to the server. After this the app could be deployed to the server, and we could both load documents and generate them on the fly.

Load balancing

Nope, we're not done yet! Just when I thought that everything is working well, I realised that the web editor sometimes is failing to load the document, and would just come up with a blank document instead. Digging around, I found yet another post on their blog talking about load balancing (you'd think this should be in the docs, no?)

Apparently, you have to make sure to enable client affinity in your load balancer to make sure that all requests from the same user will be handled by the same host. Otherwise the editor will not be happy. Luckily this was a simple change that got the editor working.

Epilogue

While I got this working, I'm not too happy with all the effort we had to go through to make it work. The docs could be quite a bit more well structured, and explained some things in detail (it's aimed at developers after all, not the end users). I shouldn't have had to rely on their blog so much either. I'm not too happy with the fact that I was forced to enable sticky sessions on my app, or the fact that my web app can crash (bringing a popup on the web server, preventing the app pool from closing and/or crashing it HARD).

My pan going forward is to try decouple the editor from my application, hosting as much of it as I can in a separate process. I'd also like to move the document generation to a separate process, away from my app. Hopefully this would reduce the risk of errors and crashes from my web app, and won't force me to enable sticky sessions in my load balancer!