Jason Shave
Jason Shave

Reputation: 2672

Can't access method (Azure Tables with ASP.NET Core)

I'm trying to access a method from another class (my controller for my view) where I make a connection to Azure Tables and update an entity.

My 'controller' makes a call as follows:

// this requires an object reference
HttpResponseMessage httpResponseMessage = AzureTableConn.UpdateTenantSettings(post);

Here is my class containing my connection to Azure Tables where I pull my connection string from Azure Key Vault:

public class AzureTableConn
{
    public AzureTableConn(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    private IConfiguration Configuration { get; set; }

    private CloudTable TableConnection(string tableName)
    {
        var connectionString = Configuration["AzureTableStorageConnectionString"];
        var cloudStorageAccount = CloudStorageAccount.Parse(connectionString);
        CloudTableClient tableClient = cloudStorageAccount.CreateCloudTableClient();
        CloudTable cloudTable = tableClient.GetTableReference(tableName);
        return cloudTable;            
    }

    public HttpResponseMessage UpdateTenantSettings(TenantSettingsModel tenantSettingsModel)
    {
        CloudTable cloudTable = TableConnection("TenantSettings");
        Task<TableResult> mergeEntity = cloudTable.ExecuteAsync(TableOperation.Merge(tenantSettingsModel));
        return new HttpResponseMessage();
    }
}

I'd like to be able to call the UpdateTenantSettings method from my controller class but it says I need an instance. This makes sense however I can't create an instance without supplying an IConfiguration object to the default constructor. I feel like I'm caught in a catch 22 and don't know enough to get around it.

My intent is to use my AzureTableConn class across my application and not create new instances of it each time I want to read/write to Azure Tables. I've tried setting my AzureTableConn class to static and while this works around the object reference error, I get issues with my reference to IConfiguration. I've also tried setting my constructor to static but that breaks it again and tells me "a static constructor must be parameterless".

I've also tried adding the following to my Startup.cs file:

services.Configure<AzureTableConnectionString>(Configuration);

Where AzureTableConnectionString is defined as:

public class AzureTableConnectionString
{
    public string ConnectionString { get; set; }
}

However, I don't know if this is right or how to implement it.

So how can I pull my Azure Key Vault configuration settings inside a class, then use methods within the same class to re-use the connection to Azure Tables, and access those methods from another class where I need to provide parameters such as the entity (data) to create/update/delete/etc.?

Upvotes: 1

Views: 279

Answers (2)

Kirk Larkin
Kirk Larkin

Reputation: 93293

You can let ASP.NET Core handle this for you by registering AzureTableConn with the DI container, like this (in ConfigureServices):

services.AddSingleton<AzureTableConn>();

In order to use this in your controller(s), just add it as a parameter to the controller's constructor and store it for later use, like so:

public class SomeController : Controller
{
    private readonly AzureTableConn _azureTableConn;

    public SomeController(AzureTableConn azureTableConn)
    {
        _azureTableConn = azureTableConn;
    }

    public IActionResult SomeAction()
    {
        ...
        var httpResponseMessage = _azureTableConn.UpdateTenantSettings(post);
        ...
    }
}

With this example, you can use _azureTableConn in any of your controller's actions. Because we've used AddSingleton, every controller will get the same instance, which will be created only once.

The docs do a good job of explaining this in much more detail: Dependency injection in ASP.NET Core.

Upvotes: 2

Nkosi
Nkosi

Reputation: 247551

Consider creating an abstraction.

public interface IAzureTableConnection {
    Task<HttpResponseMessage> UpdateTenantSettings(TenantSettingsModel tenantSettingsModel);
}

It is normally advised to avoid coupling to IConfiguration. Instead, get what you need to in the composition root and pass it to the dependent class.

Startup

private IConfiguration Configuration { get; set; }

public void ConfigureServices(IServiceCollection services) {

    //...

    var connectionString = Configuration["AzureTableStorageConnectionString"];
    var cloudStorageAccount = CloudStorageAccount.Parse(connectionString);
    CloudTableClient tableClient = cloudStorageAccount.CreateCloudTableClient();

    services.AddScoped<IAzureTableConnection>(_ => new AzureTableConnection(tableClient));

    //...
}

The dependent class would then only need to depend on configured CloudTableClient

public class AzureTableConnection: IAzureTableConnection {
    private readonly CloudTableClient tableClient;

    public AzureTableConnection(CloudTableClient tableClient) {
        this.tableClient = tableClient;
    }

    private CloudTable TableConnection(string tableName) {
        CloudTable cloudTable = tableClient.GetTableReference(tableName);
        return cloudTable;            
    }

    public async Task<HttpResponseMessage> UpdateTenantSettings(TenantSettingsModel tenantSettingsModel) {
        CloudTable cloudTable = TableConnection("TenantSettings");
        var mergeEntity = await cloudTable.ExecuteAsync(TableOperation.Merge(tenantSettingsModel));

        //...do something with the result

        return new HttpResponseMessage();
    }
}

Your controller would depend on the IAzureTableConnection abstraction explicitly via constructor injection, and have access to the injected instance when needed

public class MyController : Controller {
    private readonly IAzureTableConnection tableConnection;

    public MyController(IAzureTableConnection tableConnection) {
        this.tableConnection = tableConnection;
    }

    public async Task<IActionResult> MyAction() {


        //...

        HttpResponseMessage httpResponseMessage = await tableConnection.UpdateTenantSettings(post);

        //...
    }
}

Upvotes: 2

Related Questions