Cannot connect to cluster when using INamedBucketProvider D.I in .Net Core 3.1 Web Api

I have tried to follow this tutorial, but am unable to use a controller to read from my docker based couchbase server running at http://127.0.0.1:8091/. Am using Couchbase Server 6.6.0 on docker.

Here is my controller:

namespace CouchbaseWebApi.Controllers
{
    using Couchbase;
    using Couchbase.KeyValue;
    using CouchbaseWebApi.Interfaces;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System;

    [ApiController]
    [Route("[controller]")]
    public class RecordController : ControllerBase
    {
        private readonly ILogger<RecordController> _logger;
        private IBucket _bucket;
        private ICouchbaseCollection _collection;

        public RecordController(ILogger<RecordController> logger, ITravelBucketProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException(nameof(provider));
            }
            _logger = logger;
            InitializeCluster(provider);
        }
        private async void InitializeCluster(ITravelBucketProvider provider)
        {
            _bucket = await provider.GetBucketAsync().ConfigureAwait(false);
            _collection = await _bucket.DefaultCollectionAsync().ConfigureAwait(false);
            if (_bucket == null)
            {
                throw new ArgumentNullException(nameof(_bucket));
            }
            if (_collection == null)
            {
                throw new ArgumentNullException(nameof(_collection));
            }
        }

        [HttpPost]
        [Route("/")]
        public async void InsertRecord(CustomRecord record)
        {
            try
            {
                var key = Guid.NewGuid().ToString();
                
                await _collection.InsertAsync(key, record);
                _logger.LogInformation("Inserted record with ID: " + key);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Could not insert record due to {ex.Message}");
            }
        }
    }
}

I have provided the config in Startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddSwaggerGen();

            services.AddCouchbase(Configuration.GetSection("Couchbase"));
            services.AddCouchbaseBucket<ITravelBucketProvider>("travel-sample");
        }

Finally, I inherited the custom provider:

using Couchbase.Extensions.DependencyInjection;

namespace CouchbaseWebApi.Interfaces
{
    public interface ITravelBucketProvider : INamedBucketProvider
    {
    }
}

The appsettings.json contains:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Couchbase": {
    "Servers": [ "http://127.0.0.1" ],
    "Username": "Administrator",
    "Password": "password"
  }
}

When running this, I get null when I try to resolve the bucket in the controller.

Have tried setting the server appsettings.json to http://127.0.0.1:8091, http://localhost:8091, http://127.0.0.1, couchbase://localhost and http://localhost to no avail.
Please advise on how I can proceed. Was able to get a hold of the bucket when using the console app example, just not with a Web Api.

@Adi_S

I think the problem you are having is that you are mixing asynchronous and synchronous code. Try doing something like this:

namespace CouchbaseWebApi.Controllers
{
    using Couchbase;
    using Couchbase.KeyValue;
    using CouchbaseWebApi.Interfaces;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System;

    [ApiController]
    [Route("[controller]")]
    public class RecordController : ControllerBase
    {
        private readonly ILogger<RecordController> _logger;
        private IBucketProvider _provider;

        public RecordController(ILogger<RecordController> logger, ITravelBucketProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException(nameof(provider));
            }
            _logger = logger;
            _provider = provider;
        }

        [HttpPost]
        [Route("/")]
        public async Task InsertRecordAsync(CustomRecord record)
        {
            var bucket = await _provider.GetBucketAsync();
            var collection  = bucket.DefaultCollection();

            try
            {
                var key = Guid.NewGuid().ToString();
                
                await collection.InsertAsync(key, record);
                _logger.LogInformation("Inserted record with ID: " + key);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Could not insert record due to {ex.Message}");
            }
        }
    }
}

In C# there no concept of asynchronous constructor (it is coming I believe). You simply have DI resolve the INamedBucketProvider impl and then lazy load the IBucket in your asynchronous InsertRecordAsync method. Note the IBucket is cached within the ICluster and via DI so there is little overhead.

If this does not resolve the issue, then use SDK Dr to figure out why the connection cannot be made from the app to the docker container.

Jeff

I see, thank you!
I now see an exception when it gets to the GetBucketAsync call:

This exception was originally thrown at this call stack:
    Couchbase.Cluster.ConnectAsync(Couchbase.ClusterOptions)
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
    System.Threading.Tasks.ValueTask<TResult>.Result.get()
    System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable<TResult>.ConfiguredValueTaskAwaiter.GetResult()
    Couchbase.Extensions.DependencyInjection.Internal.BucketProvider.GetBucketAsync.AnonymousMethod__4_0(string)
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
    ...
    [Call Stack Truncated]

The message is: options must have a connection string. (Parameter 'options')
I thought the

services.AddCouchbase(Configuration.GetSection("Couchbase"));
 services.AddCouchbaseBucket<ITravelBucketProvider>("travel-sample");

in startup.cs
would handle the connection string. Are we to pass this in inside the controller?

For reference, Iā€™m using these versions of packages:
image

@Adi_S -

In your app create an appsettings.json file and paste this into it:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Couchbase": {
    "ClusterOptions": {
      "ConnectionString": "couchbase://localhost",
      "Buckets": [
        "travel-sample"
      ],
      "UserName": "Administrator",
      "Password": "password"
    }
  }
}

Then in Startup.cs the following:

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCouchbase(Configuration.GetSection("Couchbase:ClusterOptions"));
            services.AddCouchbaseBucket<ITravelSampleBucketProvider>("travel-sample");

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "cbse_10719", Version = "v1" });
            });
        }

Jeff

1 Like

Fabulous, that fixed it!

2 Likes