4 Dec 2016

Oracle Client on Windows 2016 Docker + DotNet 4.5 MVC

Docker is an interesting container technology which Microsoft is making available on their Windows Server 2016 platform.

For 4.x dotnet applications which need a connection to an Oracle database the managed Oracle Data Access Client is the easiest route to take these days, but if you must use the unmanaged version, then this blog will show you the way to setup your Docker images and what exceptions you may get if you wander from the happy path.

This blog makes the assumptions that you are familiar with building and using docker images. If you are not then visit these sites:

https://msdn.microsoft.com/en-gb/virtualization/windowscontainers/quick_start/quick_start_windows_server https://www.katacoda.com/

1. Create a demo web application

I have created a simple MVC web application to test with. The code is all found in the Global.asax.cs file. An Index method in the home controller makes a select from an Oracle database.

    using Oracle.DataAccess.Client;
    using System;
    using System.Data;
    using System.Web.Mvc;
    using System.Web.Routing;

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            RouteTable.Routes.MapRoute(name: "Default", url: "{controller}/{action}/{id}", 
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
        }
    }

    public class HomeController : Controller
    {
        public string Index()
        {
            var connection = new OracleConnection("data source=data source=oraclejp.northeurope.cloudapp.azure.com:1521/MYDATABASE;password=password;user id=jperrott;");
            connection.Open();
            var ds = new DataSet();
            new OracleDataAdapter("select * from dual", connection).Fill(ds);
            return DateTime.Now.ToShortTimeString() + ". select * from dual = " + 
                ds.Tables[0].Rows[0][0] + ", app type: " + (IntPtr.Size == 4 ? "32bit" : "64bit");
        }
    }

2. Create your Dockerfile

If you stray from the steps listed below and it you get a runtime exception, then you will need to refer to the list of exceptions further down to work out what has gone wrong.

Note: Make sure the bitness 32 or 64 is consistent for your application, OracleDataAccess.DLL ,ODAC & C++ 2010 Redistributable !

  • Download the correct ODAC (xcopy version) for your application. Either (“32-bit Oracle Data Access Components (ODAC) and NuGet Downloads”) or (“64-bit Oracle Data Access Components (ODAC) Downloads”)
  • Compile your application with a reference to the unmanaged OracleDataAccess.DLL (Use v11 if the ODAC is v11). (It in doubt use the one found in the ODAC Xcopy, located in odp.net4\odp.net\bin\4\Oracle.DataAccess.dll)

In your Dockerfile:

  • Copy your ODAC xcopy and application into the container.
  • Install the ODAC xcopy version of ODP.NET4
  • If using ODAC V12 then install Microsoft Visual C++ 2010 Redistributable Package
  • If using ODAC V12 then set Path to point at oracle client path.
  • Enable 32 bit on the app pool if you are using a web app which is 32 bit.
  • Install your application.


Dockerfile Examples

In my examples the dockerfile is located in the web application folder as are the Oracle ODAC xcopy extracted files.

alt text

Example Dockerfile for v12 32 bit
    FROM microsoft/iis

    # Install Chocolatey
    RUN @powershell -NoProfile -ExecutionPolicy unrestricted -Command "$env:chocolateyUseWindowsCompression = 'false'; (iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))) >$null 2>&1" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin

    # Install web asp.net
    RUN powershell add-windowsfeature web-asp-net45

    COPY . c:/install

    # this is where the Oracle ODAC Xcopy version has been unzipped into
    WORKDIR c:/install/oracleInstall/xcopy_12

    #install ODP.NET 4 32bit, Microsoft Visual C++ 2010 Redistributable Package, Set path to include oracle home
    RUN @powershell -NoProfile -ExecutionPolicy unrestricted -Command ".\install.bat odp.net4  c:\oracle myhome true;" \ 
        && choco install vcredist2010 -y --allow-empty-checksums; \
        && setx /m PATH "%PATH%;C:\oracle"

    SHELL ["powershell", "-command"]

    # set app pool to allow 32bit, remove default web site, add my 32 bit web application and use the 32-bit app pool
    RUN Import-Module WebAdministration; Set-ItemProperty -Path IIS:\AppPools\'.Net v4.5' -Name enable32BitAppOnWin64 -Value 'True'; \
        Remove-WebSite -Name 'Default Web Site'; \ 
        New-Website -Name 'my-app' -Port 80 -PhysicalPath 'C:\install' -ApplicationPool '.NET v4.5'

    ENTRYPOINT powershell


Example Dockerfile for v12 64 bit

The only real difference is that I am not setting the app pool to allow 32 bit.

    FROM microsoft/iis

    #Install Chocolatey
    RUN @powershell -NoProfile -ExecutionPolicy unrestricted -Command "$env:chocolateyUseWindowsCompression = 'false'; (iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))) >$null 2>&1" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin

    # Install web asp.net
    RUN powershell add-windowsfeature web-asp-net45

    COPY . c:/install

    # this is where the Oracle ODAC Xcopy version has been unzipped into
    WORKDIR c:/install/oracleInstall/xcopy_12_64

    #install ODP.NET 4 64bit, Microsoft Visual C++ 2010 Redistributable Package, Set path to include oracle home
    RUN @powershell -NoProfile -ExecutionPolicy unrestricted -Command ".\install.bat odp.net4  c:\oracle myhome true;" \ 
        && choco install vcredist2010 -y --allow-empty-checksums; \
        && setx /m PATH "%PATH%;C:\oracle"

    SHELL ["powershell", "-command"]

    # remove default web site, add my 64 bit web application and use the 64-bit app pool
    RUN Remove-WebSite -Name 'Default Web Site'; \ 
        New-Website -Name 'my-app' -Port 80 -PhysicalPath 'C:\install' -ApplicationPool '.NET v4.5'

    ENTRYPOINT powershell


3. Building the image

Build using:

    docker build -t myoraclientapp .

Example Log:

alt text


4. Running the container

Run using:

    docker run -it --rm -p 80:80 --name XXXX myoraclientapp

Your can then either use a browser to view the default page or within the docker container type:

    curl 127.0.0.1 -UseBasicParsing


5. Container run-time exceptions and what they mean

BadImageFormatException

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

This means that there is a bitness mismatch between the Application pool and the application or the application and the Oracle.DataAccess.dll.

IF App Pool has enabled 32bit apps then the app is compiled in x64 or oracle.dataaccess.dll is x64. or if if App Pool has not enable 32bit Apps then App compiled in x86 or oracle.dataaccess.dll is x86 (App pool doesn’t support x86)


BadImageFormatException: Could not load file or assembly 'Oracle.DataAccess' or one of its dependencies. An attempt was made to load a program with an incorrect format.

HttpException (0x80004005): Could not load file or assembly 'Oracle.DataAccess' or one of its dependencies. An attempt was made to load a program with an incorrect format.

The application is complied in x86, but the Oracle.DataAccess.dll is x64


OracleException

OracleException (0x80004005): The provider is not compatible with the version of Oracle client

The App pool, application and Oracle.DataAccess.dll are all 32 bit, but the ODP.NET installed on the machine is 64-bit

OracleException (0x80004005): ORA-12154: TNS:could not resolve the connect identifier specified]
  • TNSNAMES.ORA is not in the ORACLE_HOME\NETWORK\ADMIN folder.
  • ADDRESS_LIST may need to be used in TNSNAMES.ORA.


DllNotFoundException

DllNotFoundException: Unable to load DLL 'OraOps12.dll': The specified module could not be found.
  • VcRedist2010 not installed (V12 ODP.NET),
  • or using V12 OracleDataAccess.dll with V11 ODP.NET
  • or using 64 bit OracleDataAccess.dll with 32 bit ODP.NET


NullReferenceException

NullReferenceException: Object reference not set to an instance of an object.

When using the v12 ODP.NET the path must point to the client folder.


Further Investigation

If you are unable to get the container to talk to your database then I recommend creating a sandbox VM and running the steps in the dockerfile manually and see if you can reproduce the issue.

Windows Sysinternals Process Monitor is useful to determine which dll is missing on your sandbox vm. The log of the application can also be compared to the log from a working version if you have one.

blog comments powered by Disqus

About Me


My first computer was a Commodore VIC-20, I had great fun trying to code text adventures and side scrolling shoot ‘em ups in BASIC. This helped me lead the way as the first in my school to pass a computer exam.

Currently I work as a Senior Software Engineer in Bedford for a FTSE 100 Company. Coding daily in C#, JavaScript and SQL. Outside of work I work on whatever is interesting me at that time.