Q:

Cubemap in OpenTK

Warning: Total OpenGL/TK newb, so be kind.  I may have bitten off more than I can chew. 
Specs:

Visual studio Community 2017 
C# .Net 4.6.1 (using WPF, WindowsFormsHost control) 
OpenTK 3.0.1 
OpenTK.GLControl 3.0.1

I am trying to replicate the Cubemap tutorial from learnopengl.com (which I managed to get working).  I am more familiar with C# so it would be better to get a solution working with OpenTK.  All I am getting is a blank screen.  Perhaps the back of an image (too optimistic?).  Any help would be appreciated.  
Let me know if I have left anything out. 
Here is the code: (I feel like I am close.) 
MainWindow.xaml.cs
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics. OpenGL;
using System;
using System. Collections. Generic;
using System. Diagnostics;
using System. Windows;

using System. Windows. Forms;
using Path = System. IO.Path;

namespace OpenTKTesting
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        private int _vertexBufferObject;
        private int _vertexArrayObject;
        private Shader shader;

        // For documentation on this, check Texture.cs
        private TextureCubemap cubemap;

        private Camera camera;

        GLControl glControl;    // the winforms opentk control

        public MainWindow()
        {
            InitializeComponent();

            glControl = new GLControl(new GraphicsMode(32, 24, 0, 8)) { VSync = true };

            windowsFormsHost. Child = glControl;

            System. Windows. Forms. Integration. WindowsFormsHost. EnableWindowsFormsInterop();

            Toolkit. Init();
        }

        private List<string> ImageFaces
        {
            get;
            set;
        }

        public void GetImageFaces()
        {
            ImageFaces = new List<string>();
            string dir = @"C:\\Development\\ScratchDev\\OpenTKTesting\\OpenTKTesting\\Resources";
            ImageFaces. Add(Path. Combine(dir, "f.jpg"));
            ImageFaces. Add(Path. Combine(dir, "b.jpg"));
            ImageFaces. Add(Path. Combine(dir, "u.jpg"));
            ImageFaces. Add(Path. Combine(dir, "d.jpg"));
            ImageFaces. Add(Path. Combine(dir, "r.jpg"));
            ImageFaces. Add(Path. Combine(dir, "l.jpg"));
        }

        private void SetupGLControl()
        {
            if (glControl == null)
            {
                return;
            }

            GetImageFaces();
            foreach (var item in ImageFaces)
            {
                Debug. WriteLine(item);
            }

            glControl. MakeCurrent();
            glControl. VSync = true;

            glControl. Resize += GlControl_Resize;
            glControl. Paint += GlControl_Paint;
            shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
            shader. SetInt("cubeMapArray", 0);

            // We initialize the camera so that it is 3 units back from where the rectangle is
            // and give it the proper aspect ratio
            camera = new Camera(Vector3.UnitZ, glControl. AspectRatio);

        }

        private void GlControl_Paint(object sender, PaintEventArgs e)
        {
            GL.Clear(ClearBufferMask. ColorBufferBit | ClearBufferMask. DepthBufferBit);

            _vertexBufferObject = GL.GenBuffer();
            GL.BindBuffer(BufferTarget. ArrayBuffer, _vertexBufferObject);
            GL.BufferData(BufferTarget. ArrayBuffer, _vertices. Length * sizeof(float), _vertices, BufferUsageHint. StaticDraw);

            cubemap = new TextureCubemap(ImageFaces);
            cubemap. UseCubemap();

            _vertexArrayObject = GL.GenVertexArray();
            GL.BindVertexArray(_vertexArrayObject);

            GL.BindBuffer(BufferTarget. ArrayBuffer, _vertexBufferObject);
            //GL.BindBuffer(BufferTarget. ElementArrayBuffer, _elementBufferObject);

            var vertexLocation = shader. GetAttribLocation("aPos");
            GL.EnableVertexAttribArray(vertexLocation);
            GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType. Float, false, 3 * sizeof(float), 0);

            //// Next, we also setup texture coordinates.  It works in much the same way. 
            //// We add an offset of 3, since the first vertex coordinate comes after the first vertex
            //// and change the amount of data to 2 because there's only 2 floats for vertex coordinates
            //var texCoordLocation = shader. GetAttribLocation("TexCoords");
            //GL.EnableVertexAttribArray(texCoordLocation);
            //GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType. Float, false, 5 * sizeof(float), 3 * sizeof(float));

            GL.Clear(ClearBufferMask. ColorBufferBit);

            cubemap. UseCubemap();

            shader. Use();            

            shader. SetMatrix4("view", camera. GetViewMatrix());
            shader. SetMatrix4("projection", camera. GetProjectionMatrix());

            GL.DrawArrays(PrimitiveType. Triangles, 0, 36);

            glControl. SwapBuffers();
        }

        private readonly float[] _vertices =
        {
            // positions          
            -1.0f,  1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
             1.0f, -1.0f, -1.0f,
             1.0f, -1.0f, -1.0f,
             1.0f,  1.0f, -1.0f,
            -1.0f,  1.0f, -1.0f,

            -1.0f, -1.0f,  1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f,  1.0f, -1.0f,
            -1.0f,  1.0f, -1.0f,
            -1.0f,  1.0f,  1.0f,
            -1.0f, -1.0f,  1.0f,

             1.0f, -1.0f, -1.0f,
             1.0f, -1.0f,  1.0f,
             1.0f,  1.0f,  1.0f,
             1.0f,  1.0f,  1.0f,
             1.0f,  1.0f, -1.0f,
             1.0f, -1.0f, -1.0f,

            -1.0f, -1.0f,  1.0f,
            -1.0f,  1.0f,  1.0f,
             1.0f,  1.0f,  1.0f,
             1.0f,  1.0f,  1.0f,
             1.0f, -1.0f,  1.0f,
            -1.0f, -1.0f,  1.0f,

            -1.0f,  1.0f, -1.0f,
             1.0f,  1.0f, -1.0f,
             1.0f,  1.0f,  1.0f,
             1.0f,  1.0f,  1.0f,
            -1.0f,  1.0f,  1.0f,
            -1.0f,  1.0f, -1.0f,

            -1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f,  1.0f,
             1.0f, -1.0f, -1.0f,
             1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f,  1.0f,
             1.0f, -1.0f,  1.0f
        };

        private void GlControl_Resize(object sender, EventArgs e)
        {
            InitializeView();

            Debug. WriteLine("Resizing...");            
        }

        private void WindowsFormsHost_Loaded(object sender, RoutedEventArgs e)
        {
            Debug. WriteLine("WFH Loaded...");
            SetupGLControl();
        }

        public void InitializeView()
        {
            double newWidth = glControl. ClientSize. Width;
            double newHeight = glControl. ClientSize. Height;

            GL.Viewport(0, 0, (int)newWidth, (int)newHeight);

            // We enable depth testing here.  If you try to draw something more complex than one plane without this,
            // you'll notice that polygons further in the background will occasionally be drawn over the top of the ones in the foreground. 
            // Obviously, we don't want this, so we enable depth testing.  We also clear the depth buffer in GL.Clear over in OnRenderFrame. 
            GL.Enable(EnableCap. DepthTest);

            glControl. SwapBuffers();
        }

    }
}

TextureCubemap.cs
using OpenTK.Graphics. OpenGL;
using System;
using System. Collections. Generic;
using System. Diagnostics;
using System. Drawing;
using System. Drawing. Imaging;
using System. Linq;
using System. Text;
using System. Threading. Tasks;
using PixelFormat = OpenTK.Graphics. OpenGL.PixelFormat;

namespace OpenTKTesting
{
    class TextureCubemap
    {
        public readonly int Handle;

        // Create texture from path. 
        public TextureCubemap(List<string> imagePaths)
        {
            // Generate handle
            Handle = GL.GenTexture();

            // Bind the handle
            UseCubemap();

            for (int i = 0; i < imagePaths. Count; i++)

            {
                // Load the image
                using (var image = new Bitmap(imagePaths[i]))
                {
                    Debug. WriteLine(imagePaths[i]);

                    var data = image. LockBits(
                        new Rectangle(0, 0, image. Width, image. Height),
                        ImageLockMode. ReadOnly,
                        System. Drawing. Imaging. PixelFormat. Format32bppRgb);

                    GL.TexImage2D(TextureTarget. TextureCubeMap,
                        0,
                        PixelInternalFormat. Rgb,
                        image. Width,
                        image. Height,
                        0,
                        PixelFormat. Rgb,
                        PixelType. UnsignedByte,
                        data. Scan0);

                }
            }

            GL.TexParameter(TextureTarget. TextureCubeMap, TextureParameterName. TextureMagFilter, (int)TextureMagFilter. Linear);
            GL.TexParameter(TextureTarget. TextureCubeMap, TextureParameterName. TextureMinFilter, (int)TextureMinFilter. Linear);
            GL.TexParameter(TextureTarget. TextureCubeMap, TextureParameterName. TextureWrapS, (int)TextureParameterName. ClampToEdge);
            GL.TexParameter(TextureTarget. TextureCubeMap, TextureParameterName. TextureWrapT, (int)TextureParameterName. ClampToEdge);
            GL.TexParameter(TextureTarget. TextureCubeMap, TextureParameterName. TextureWrapR, (int)TextureParameterName. ClampToEdge);

        }

        public void UseCubemap(TextureUnit unit = TextureUnit. Texture0)
        {
            GL.ActiveTexture(unit);
            GL.BindTexture(TextureTarget. TextureCubeMap, Handle);
        }

    }
}

Shader.cs
using System;
using System. Collections. Generic;
using System. IO;
using System. Linq;
using System. Text;
using System. Threading. Tasks;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics. OpenGL;

namespace OpenTKTesting
{
    // A simple class meant to help create shaders. 
    public class Shader
    {
        public readonly int Handle;

        private readonly Dictionary<string, int> _uniformLocations;

        // This is how you create a simple shader. 
        // Shaders are written in GLSL, which is a language very similar to C in its semantics. 
        // The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on. 
        // A commented example of GLSL can be found in shader.vert
        public Shader(string vertPath, string fragPath)
        {
            // There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders. 
            // The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader. 
            //   The vertex shader won't be too important here, but they'll be more important later. 
            // The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel. 
            //   The fragment shader is what we'll be using the most here. 

            // Load vertex shader and compile
            // LoadSource is a simple function that just loads all text from the file whose path is given. 
            var shaderSource = LoadSource(vertPath);

            // GL.CreateShader will create an empty shader (obviously).  The ShaderType enum denotes which type of shader will be created. 
            var vertexShader = GL.CreateShader(ShaderType. VertexShader);

            // Now, bind the GLSL source code
            GL.ShaderSource(vertexShader, shaderSource);

            // And then compile
            CompileShader(vertexShader);

            // We do the same for the fragment shader
            shaderSource = LoadSource(fragPath);
            var fragmentShader = GL.CreateShader(ShaderType. FragmentShader);
            GL.ShaderSource(fragmentShader, shaderSource);
            CompileShader(fragmentShader);

            // These two shaders must then be merged into a shader program, which can then be used by OpenGL. 
            // To do this, create a program...
            Handle = GL.CreateProgram();

            // Attach both shaders...
            GL.AttachShader(Handle, vertexShader);
            GL.AttachShader(Handle, fragmentShader);

            // And then link them together. 
            LinkProgram(Handle);

            // When the shader program is linked, it no longer needs the individual shaders attacked to it; the compiled code is copied into the shader program. 
            // Detach them, and then delete them. 
            GL.DetachShader(Handle, vertexShader);
            GL.DetachShader(Handle, fragmentShader);
            GL.DeleteShader(fragmentShader);
            GL.DeleteShader(vertexShader);

            // The shader is now ready to go, but first, we're going to cache all the shader uniform locations. 
            // Querying this from the shader is very slow, so we do it once on initialization and reuse those values
            // later. 

            // First, we have to get the number of active uniforms in the shader. 
            GL.GetProgram(Handle, GetProgramParameterName. ActiveUniforms, out var numberOfUniforms);

            // Next, allocate the dictionary to hold the locations. 
            _uniformLocations = new Dictionary<string, int>();

            // Loop over all the uniforms,
            for (var i = 0; i < numberOfUniforms; i++)
            {
                // get the name of this uniform,
                var key = GL.GetActiveUniform(Handle, i, out _, out _);

                // get the location,
                var location = GL.GetUniformLocation(Handle, key);

                // and then add it to the dictionary. 
                _uniformLocations. Add(key, location);
            }
        }

        private static void CompileShader(int shader)
        {
            // Try to compile the shader
            GL.CompileShader(shader);

            // Check for compilation errors
            GL.GetShader(shader, ShaderParameter. CompileStatus, out var code);
            if (code ! = (int)All. True)
            {
                // We can use `GL.GetShaderInfoLog(shader)` to get information about the error. 
                throw new Exception($"Error occurred whilst compiling Shader({shader})");
            }
        }

        private static void LinkProgram(int program)
        {
            // We link the program
            GL.LinkProgram(program);

            // Check for linking errors
            GL.GetProgram(program, GetProgramParameterName. LinkStatus, out var code);
            if (code ! = (int)All. True)
            {
                // We can use `GL.GetProgramInfoLog(program)` to get information about the error. 
                throw new Exception($"Error occurred whilst linking Program({program})");
            }
        }

        // A wrapper function that enables the shader program. 
        public void Use()
        {
            GL.UseProgram(Handle);
        }

        // The shader sources provided with this project use hardcoded layout(location)-s.  If you want to do it dynamically,
        // you can omit the layout(location=X) lines in the vertex shader, and use this in VertexAttribPointer instead of the hardcoded values. 
        public int GetAttribLocation(string attribName)
        {
            return GL.GetAttribLocation(Handle, attribName);
        }

        // Just loads the entire file into a string. 
        private static string LoadSource(string path)
        {
            using (var sr = new StreamReader(path, Encoding. UTF8))
            {
                return sr. ReadToEnd();
            }
        }

        // Uniform setters
        // Uniforms are variables that can be set by user code, instead of reading them from the VBO. 
        // You use VBOs for vertex-related data, and uniforms for almost everything else. 

        // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
        //     1.  Bind the program you want to set the uniform on
        //     2.  Get a handle to the location of the uniform with GL.GetUniformLocation. 
        //     3.  Use the appropriate GL.Uniform* function to set the uniform. 

        /// <summary>
        /// Set a uniform int on this shader. 
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        public void SetInt(string name, int data)
        {
            GL.UseProgram(Handle);
            GL.Uniform1(_uniformLocations[name], data);
        }

        /// <summary>
        /// Set a uniform float on this shader. 
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        public void SetFloat(string name, float data)
        {
            GL.UseProgram(Handle);
            GL.Uniform1(_uniformLocations[name], data);
        }

        /// <summary>
        /// Set a uniform Matrix4 on this shader
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        /// <remarks>
        ///   <para>
        ///   The matrix is transposed before being sent to the shader. 
        ///   </para>
        /// </remarks>
        public void SetMatrix4(string name, Matrix4 data)
        {
            GL.UseProgram(Handle);
            GL.UniformMatrix4(_uniformLocations[name], true, ref data);
        }

        /// <summary>
        /// Set a uniform Vector3 on this shader. 
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        public void SetVector3(string name, Vector3 data)
        {
            GL.UseProgram(Handle);
            GL.Uniform3(_uniformLocations[name], data);
        }
    }
}

Camera.cs
using OpenTK;
using System;
using System. Collections. Generic;
using System. Linq;
using System. Text;
using System. Threading. Tasks;

namespace OpenTKTesting
{
    // This is the camera class as it could be set up after the tutorials on the website
    // It is important to note there are a few ways you could have set up this camera, for example
    // you could have also managed the player input inside the camera class, and a lot of the properties could have
    // been made into functions. 

    // TL;DR: This is just one of many ways in which we could have set up the camera
    // Check out the web version if you don't know why we are doing a specific thing or want to know more about the code
    public class Camera
    {
        // Those vectors are directions pointing outwards from the camera to define how it rotated
        private Vector3 _front = -Vector3.UnitZ;
        private Vector3 _up = Vector3.UnitY;
        private Vector3 _right = Vector3.UnitX;

        // Rotation around the X axis (radians)
        private float _pitch;
        // Rotation around the Y axis (radians)
        private float _yaw = -MathHelper. PiOver2; // Without this you would be started rotated 90 degrees right
        // The field of view of the camera (radians)
        private float _fov = MathHelper. PiOver2;

        public Camera(Vector3 position, float aspectRatio)
        {
            Position = position;
            AspectRatio = aspectRatio;
        }

        // The position of the camera
        public Vector3 Position { get; set; }
        // This is simply the aspect ratio of the viewport, used for the projection matrix
        public float AspectRatio { private get; set; }

        public Vector3 Front => _front;
        public Vector3 Up => _up;
        public Vector3 Right => _right;

        // We convert from degrees to radians as soon as the property is set to improve performance
        public float Pitch
        {
            get => MathHelper. RadiansToDegrees(_pitch);
            set
            {
                // We clamp the pitch value between -89 and 89 to prevent the camera from going upside down, and a bunch
                // of weird "bugs" when you are using euler angles for rotation. 
                // If you want to read more about this you can try researching a topic called gimbal lock
                var angle = MathHelper. Clamp(value, -89f, 89f);
                _pitch = MathHelper. DegreesToRadians(angle);
                UpdateVectors();
            }
        }

        // We convert from degrees to radians as soon as the property is set to improve performance
        public float Yaw
        {
            get => MathHelper. RadiansToDegrees(_yaw);
            set
            {
                _yaw = MathHelper. DegreesToRadians(value);
                UpdateVectors();
            }
        }

        // The field of view (FOV) is the vertical angle of the camera view, this has been discussed more in depth in a
        // previous tutorial, but in this tutorial you have also learned how we can use this to simulate a zoom feature. 
        // We convert from degrees to radians as soon as the property is set to improve performance
        public float Fov
        {
            get => MathHelper. RadiansToDegrees(_fov);
            set
            {
                var angle = MathHelper. Clamp(value, 1f, 45f);
                _fov = MathHelper. DegreesToRadians(angle);
            }
        }

        // Get the view matrix using the amazing LookAt function described more in depth on the web tutorials
        public Matrix4 GetViewMatrix()
        {
            return Matrix4.LookAt(Position, Position + _front, _up);
        }

        // Get the projection matrix using the same method we have used up until this point
        public Matrix4 GetProjectionMatrix()
        {
            return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f);
        }

        // This function is going to update the direction vertices using some of the math learned in the web tutorials
        private void UpdateVectors()
        {
            // First the front matrix is calculated using some basic trigonometry
            _front. X = (float)Math. Cos(_pitch) * (float)Math. Cos(_yaw);
            _front. Y = (float)Math. Sin(_pitch);
            _front. Z = (float)Math. Cos(_pitch) * (float)Math. Sin(_yaw);

            // We need to make sure the vectors are all normalized, as otherwise we would get some funky results
            _front = Vector3.Normalize(_front);

            // Calculate both the right and the up vector using cross product
            // Note that we are calculating the right from the global up, this behaviour might
            // not be what you need for all cameras so keep this in mind if you do not want a FPS camera
            _right = Vector3.Normalize(Vector3.Cross(_front, Vector3.UnitY));
            _up = Vector3.Normalize(Vector3.Cross(_right, _front));
        }
    }
}

shader.vert
#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    gl_Position = projection * view * vec4(aPos, 1.0);
}  

shader.frag
#version 330 core
out vec4 FragColor;

in vec3 texDir;
in vec3 TexCoords;

uniform samplerCube cubeMapArray;

void main()
{    
    FragColor = texture(cubeMapArray, TexCoords);
}

Edits:
@Rabbid76 - I've made the updates as you suggested.  Unfortunately I am still getting the black screen.  I more or less follow your suggestions.  Are you able to suggest anything else, perhaps related to the shaders or the camera? 
private void SetupGLControl()
        {
            if (glControl == null)
            {
                return;
            }

            GetImageFaces();
            foreach (var item in ImageFaces)
            {
                Debug. WriteLine(item);
            }

            glControl. MakeCurrent();
            glControl. VSync = true;

            glControl. Resize += GlControl_Resize;
            glControl. Paint += GlControl_Paint;
            shader = new Shader("Shaders/shader.vert", "Shaders/shader.frag");
            shader. SetInt("cubeMapArray", 0);

            // We initialize the camera so that it is 3 units back from where the rectangle is
            // and give it the proper aspect ratio
            camera = new Camera(Vector3.UnitZ, glControl. AspectRatio);

            cubemap = new TextureCubemap(ImageFaces);

            _vertexBufferObject = GL.GenBuffer();
            GL.BindBuffer(BufferTarget. ArrayBuffer, _vertexBufferObject);
            GL.BufferData(BufferTarget. ArrayBuffer, _vertices. Length * sizeof(float), _vertices, BufferUsageHint. StaticDraw);

            _vertexArrayObject = GL.GenVertexArray();
            GL.BindVertexArray(_vertexArrayObject);

            GL.BindBuffer(BufferTarget. ArrayBuffer, _vertexBufferObject);

            var vertexLocation = shader. GetAttribLocation("aPos");
            GL.EnableVertexAttribArray(vertexLocation);
            GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType. Float, false, 3 * sizeof(float), 0);
        }

        private void GlControl_Paint(object sender, PaintEventArgs e)
        {
            GL.Clear(ClearBufferMask. ColorBufferBit | ClearBufferMask. DepthBufferBit);

            cubemap. UseCubemap();

            shader. Use();                   

            shader. SetMatrix4("view", camera. GetViewMatrix());
            shader. SetMatrix4("projection", camera. GetProjectionMatrix());

            GL.BindVertexArray(_vertexArrayObject);

            GL.DrawArrays(PrimitiveType. Triangles, 0, 36);

            glControl. SwapBuffers();
        }

A:

When you specify the a two-dimensional texture image of a cube map texture, then you specify a single side of the cube map and the target has to be one of
TextureCubeMapNegativeX, TextureCubeMapNegativeY, TextureCubeMapNegativeZ, TextureCubeMapPositiveX, TextureCubeMapPositiveY or TextureCubeMapPositiveZ:
TextureTarget[] targets = 
{
   TextureTarget. TextureCubeMapNegativeX, TextureTarget. TextureCubeMapNegativeY,
   TextureTarget. TextureCubeMapNegativeZ, TextureTarget. TextureCubeMapPositiveX,
   TextureTarget. TextureCubeMapPositiveY, TextureTarget. TextureCubeMapPositiveZ
} 

for (int i = 0; i < imagePaths. Count; i++)
{
    // Load the image
    using (var image = new Bitmap(imagePaths[i]))
    {
        Debug. WriteLine(imagePaths[i]);

        var data = image. LockBits(
            new Rectangle(0, 0, image. Width, image. Height),
            ImageLockMode. ReadOnly,
            System. Drawing. Imaging. PixelFormat. Format32bppRgb);

        GL.TexImage2D(targets[i], // <------
            0,
            PixelInternalFormat. Rgb,
            image. Width,
            image. Height,
            0,
            PixelFormat. Rgb,
            PixelType. UnsignedByte,
            data. Scan0);
    }
}

The buffer object and vertex array object is created in every frame.  Create the objects once at initialization and use it in every frame.  Note, you dont' delete the objects, the GPU has no "garbage collection". 
Do the Vertex Specification in  MainWindow. SetupGLControl.  It is sufficient to bind the Vertex Array Object before the draw call:
public partial class MainWindow : Window
{
    private TextureCubemap cubemap;

    // [...]

    private void SetupGLControl()
    {
        // [...]

        cubemap = new TextureCubemap(ImageFaces);

        _vertexBufferObject = GL.GenBuffer();
        GL.BindBuffer(BufferTarget. ArrayBuffer, _vertexBufferObject);
        GL.BufferData(BufferTarget. ArrayBuffer, _vertices. Length * sizeof(float), _vertices, BufferUsageHint. StaticDraw);

        _vertexArrayObject = GL.GenVertexArray();
        GL.BindVertexArray(_vertexArrayObject);

        GL.BindBuffer(BufferTarget. ArrayBuffer, _vertexBufferObject);

        var vertexLocation = shader. GetAttribLocation("aPos");
        GL.EnableVertexAttribArray(vertexLocation);
        GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType. Float, false, 3 * sizeof(float), 0);

        //GL.BindBuffer(BufferTarget. ElementArrayBuffer, _elementBufferObject);
        // [...]
    }

    private void GlControl_Paint(object sender, PaintEventArgs e)
    {
        GL.Clear(ClearBufferMask. ColorBufferBit | ClearBufferMask. DepthBufferBit);

        cubemap. UseCubemap();

        shader. Use();

        shader. SetMatrix4("view", camera. GetViewMatrix());
        shader. SetMatrix4("projection", camera. GetProjectionMatrix());

        GL.BindVertexArray(_vertexArrayObject); // <------ bind vertex array object

        GL.DrawArrays(PrimitiveType. Triangles, 0, 36);

        glControl. SwapBuffers();
    }

    // [...]
}
``´

