DinkToPdf in Docker

To generate PDFs from HTML using DinkToPdf in a linux based container, the libwkhtmltox.so library is required.

One way to do this is to simply include libwkhtmltox in your version control system, but it takes up a lot of space (43 MB) thereby increasing the time for initial checkouts considerably. A better way, if your app is containerized, is to let the Dockerfile define how to fetch libwkhtmltox when building the base image for your app.

The following examples are based on the lightweight Debian linux image, more specifically “aspnet:5.0-buster-slim”.

Due to its slimmed down nature, the “aspnet:5.0-buster-slim” image does not even have wget installed, so we will have to install that along with other dependencies needed by wkhtmltopdf. Add the following to the “base” stage in your Dockerfile:

RUN apt update
RUN apt install -y libgdiplus
RUN ln -s /usr/lib/libgdiplus.so /lib/x86_64-linux-gnu/libgdiplus.so
RUN apt-get install -y --no-install-recommends zlib1g fontconfig libfreetype6 libx11-6 libxext6 libxrender1 wget gdebi
RUN wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb
RUN gdebi --n wkhtmltox_0.12.5-1.stretch_amd64.deb
RUN ln -s /usr/local/lib/libwkhtmltox.so /usr/lib/libwkhtmltox.so

Libgdiplus is a replacement for System.Drawing required by DinkToPdf to work in a linux environment.

It is important that symbolic links are made to libgdiplus.so and libwkhtmltopdf in /usr/lib. Many examples of getting DinkToPdf to work save the files in the /app directory but this will not work while debugging the dockerized app in Visual Studio. Downloading to the /app dir using wget seems to works without errors when monitoring the build output, but the file simply does not appear in the /app directory in the final image. The reason for this is, that in Visual Studio, the /app directory is a Volume mapped to the directory on the host machine containing the source code. A little gotcha that can take a few hours of debugging to track down 🙂

The following is a complete Dockerfile utilizing the lines from above to enable PDF generation inside a Debian linux based Docker container.

FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
WORKDIR /app

RUN apt update
RUN apt install -y libgdiplus
RUN ln -s /usr/lib/libgdiplus.so /lib/x86_64-linux-gnu/libgdiplus.so
RUN apt-get install -y –no-install-recommends zlib1g fontconfig libfreetype6 libx11-6 libxext6 libxrender1 wget gdebi
RUN wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb
RUN gdebi –n wkhtmltox_0.12.5-1.stretch_amd64.deb
RUN ln -s /usr/local/lib/libwkhtmltox.so /usr/lib/libwkhtmltox.so
ENV ASPNETCORE_URLS=http://+:80
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
WORKDIR /src
COPY [“BooksOnline.PdfService/BooksOnline.PdfService.csproj”, “BooksOnline.PdfService/”]
RUN dotnet restore “BooksOnline.PdfService/BooksOnline.PdfService.csproj”
COPY . .
WORKDIR “/src/BooksOnline.PdfService”
RUN dotnet build “BooksOnline.PdfService.csproj” -c Release -o /app/build

FROM build AS publish
RUN dotnet publish “BooksOnline.PdfService.csproj” -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY –from=publish /app/publish .
ENTRYPOINT [“dotnet”, “BooksOnline.PdfService.dll”]

HTTPS Redirect with .htaccess

Creating a simple HTTPS redirect rule for Apache can potentially be a long, tiresome journey of trial and error.
There are many ways to write the rule with subtle syntax differences and other factors such as the version of the Apache server also affecting the outcome.
The following snippet is what finally worked for me on Gigahost (a shared web hosting provider in Denmark).

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{ENV:HTTPS} !on
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

Forcing Retrieval of Images in ASP.NET/MVC

Browsers cache images in order to speed up page load times. If an action on a controller generates dynamic images, this can be a problem, since the user will only see the first image fetched by the browser. One way to force the browser to by-pass its cache and actually retrieve the image is to append a random parameter to the URL for the action.
For example: A user uploads an image to the server and the server subsequently returns some HTML showing the image. The user then performs an action which triggers the server to alter the image in some way (for example cropping or resizing), and returns the HTML once again. However, the browser sees that the link to the image is the same, and therefore it simply shows the initial image which it has already cached.

So, instead of generating the image URL with razor syntax like this:

<img src="@Url.Action("GetPhoto")" />

Simply add some random parameter (or just anything the browser has not seen before) to force it to download the image again:

<img src="@Url.Action("GetPhoto", new {ticks = DateTime.UtcNow.Ticks})" />

Extracting Image Files from SQL Server

There are a number of different plugins and tools which can export image data in hex format from an MS SQL server to an actual file on the harddrive. If you have Visual Studio installed, however, the easiest way is to just write a small console program to select the data from the image column and save it to a file. For example:

static void Main(string[] args)
{
try
{
SqlConnection thisConnection = new SqlConnection(@"Server=192.168.1.1,1433;database=MyDb;User id=sa;Password=JdjdUD78!;");
thisConnection.Open();
SqlCommand thisCommand = thisConnection.CreateCommand();
thisCommand.CommandText = "SELECT ImageData FROM [MyDatabase].[dbo].[Images] where ID = 1";
byte[] imgBytes = (byte[]) thisCommand.ExecuteScalar();
FileStream fs = new FileStream(@"C:\temp\image.jpg", FileMode.CreateNew, FileAccess.Write);
fs.Write(imgBytes, 0, imgBytes.Length);
fs.Flush();
fs.Close();
thisConnection.Close();
}
catch (SqlException e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}

Basic SQL Server Query in C#

If you need to access a database on an MSSQL server programmatically (in C#) just to do some quick and dirty data extraction and perhaps manipulation, the following code snippet will get you started. Replace the connection string, table and columns names with whatever is appropriate for your task.

static void Main(string[] args)
{
try
{
SqlConnection thisConnection = new SqlConnection(@"Server=192.168.1.1,1433;database=NorthWind;User id=sa;Password=HIUHFuhf8;");
thisConnection.Open();
SqlCommand thisCommand = thisConnection.CreateCommand();
thisCommand.CommandText = "SELECT [ID], [Name] FROM Customers";
SqlDataReader thisReader = thisCommand.ExecuteReader();
while (thisReader.Read())
{
Console.WriteLine("\t{0}\t{1}", thisReader["ID"], thisReader["Name"]);
}
thisReader.Close();
thisConnection.Close();
}
catch (SqlException e)
{
Console.WriteLine(e.Message);
}
Console.ReadLine();
}

Subversion revision number in AssemblyInfo.cs

Pre-requisites: Tortoise SVN, Visual Studio
  • Exclude the existing “AssemblyInfo.cs” file from Subversion
  • Rename “AssemblyInfo.cs” to “AssemblyInfo.template
  • Add the Subversion revision keyword to the template file. For example: [assembly : AssemblyVersion(“1.0.0.$WCREV$“)]
  • Add SubWCRev.exe from the TortoiseSVN bin directory to the project (or add the Tortoise SVN bin folder to your path)
  • In the pre-build event for your project, add the following line (You may need to adjust it to your own project structure): "$(SolutionDir)\lib\SubWCRev\subwcrev.exe" "$(SolutionDir)." "$(ProjectDir)Properties\AssemblyInfo.template" "$(ProjectDir)Properties\AssemblyInfo.cs"
  • Done! The “AssemblyVersion.cs” file should now be auto-generated on every build and the $WCREV$ keyword is replaced with the current SVN revision number on the solution directory.


XBMC + TVHeadEnd + HDHomeRun

Guide

How to install and configure TVHeadend and HDHomerun on an OpenELEC machine.

Installation

HDHomerun Driver
TVHeadEnd

Configuration

Configure HDHomerun Driver

Change directory to /storage/.xbmc/addons/driver.dvb.hdhomerun/config
Make a copy of dvbhdhomerun.sample to dvbhdhomerun.conf

Open \\xbmc\Userdata\addon_data\driver.dvb.hdhomerun\adapters and copy the IDs of the two tuners to dvbhdhomerun and set their types (just follow the instructions in the config file.). Alternatively, run userhdhomerun to get the IDs.

Reboot.

Configure TVHeadend

Go to webpage of TVHeadend: http://:9981
Go to Configuration -> DVB Inputs -> TV Adapters

Select the first adapter.
Click “Add DVB Network by Location”
Choose your location
Click “Enabled”
Click “Save”
TVHeadend now starts scanning for channels. You can monitor its progress in the pane to the right. When “Muxes awaiting initial scan” is zero, it’s done.
Click “Map DVB Services to Channels”.
The mapping will also take some minutes to complete depending on how many channels are present.

In the web config in Configuration -> TV Adapters: After all channels have been configured, disable “Autodetect Muxes” and “Idle Scanning“. Having these enabled completely destroyed the stream from TVHeadEnd, making the image look garbled and stuttering. It looked like a low bandwidth connection or bad signal.

Samba Sharing in XBMCbuntu

Samba is not included with XBMCbuntu (at least in 11.10), so first we have to install it:
sudo apt-get install samba
Open Samba’s configuration file in an editor
sudo nano /etc/samba/smb.conf
Scroll down to the bottom of the file and add the following sections, which will create a public share with read/write access without password validation:

[global]
workgroup = Workgroup
netbios name = XBMC
server string = XBMC Server
log file = /var/log/samba/log.%m
max log size = 50
map to guest = bad user
socket options = TCP_NODELAY SO_RCVBUF=8192 SO_SNDBUF=8192
local master = no
dns proxy = no


[public]
path = /media
public = yes
only guest = yes
writable = yes
force user = xbmc

The above configuration example shares everything under the /media directory in a folder called “public”. The “force user” property must be set to a user with write access to the directory being shared. All files and directories will appear to have been created by this user.
Run “testparm” to check if your Samba configuration is parseable. For configuration changes to take effect, the Samba daemon can be restarted with
sudo /etc/init.d/smbd restart
The following screenshot shows my XBMC box sharing the /media directory which contains mount points for two harddrives (ingeniously named according to their capacities…):