17 min read

This article, written by Roberto Freato and Marco Parenzan, is from the book Mastering Cloud Development using Microsoft Azure by Packt Publishing, and it teaches us how to create multitenant applications in Azure. This book guides you through the many efficient ways of mastering the cloud services and using Microsoft Azure and its services to its maximum capacity.

(For more resources related to this topic, see here.)

A tenant is a private space for a user or a group of users in an application. A typical way to identify a tenant is by its domain name. If multiple users share a domain name, we say that these users live inside the same tenant. If a group of users use a different reserved domain name, they live in a reserved tenant. From this, we can infer that different names are used to identify different tenants. Different domain names can imply different app instances, but we cannot say the same about deployed resources.

Multitenancy is one of the funding principles of Cloud Computing. Developers need to reach economy of scale, which allows every cloud user to scale as needed without paying for overprovisioned resources or suffering for underprovisioned resources. To do this, cloud infrastructure needs to be oversized for a single user and sized for a pool of potential users that share the same group of resources during a certain period of time.

Multitenancy is a pattern. Legacy on-premise applications usually tend to be a single-tenant app, shared between users because of the lack of specific DevOps tasks. Provisioning an app for every user can be a costly operation.

Cloud environments invite reserving a single tenant for each user (or group of users) to enforce better security policies and to customize tenants for specific users because all DevOps tasks can be automated via management APIs.

The cloud invites reserving resource instances for a tenant and deploying a group of tenants on the same resources. In general, this is a new way of handling app deployment. We will now take a look at how to develop an app in this way.

Scenario

CloudMakers.xyz, a cloud-based development company, decided to develop a personal accountant web application—MyAccountant. Professionals or small companies can register themselves on this app as a single customer and record all of their invoices on it. A single customer represents the tenant; different companies use different tenants.

Every tenant needs its own private data to enforce data security, so we will reserve a dedicated database for a single tenant. Access to a single database is not an intensive task because invoice registration will generally occur once daily.

Every tenant will have its own domain name to enforce company identity.

A new tenant can be created from the company portal application, where new customers register themselves, specifying the tenant name.

For sample purposes, without the objective of creating production-quality styling, we use the default ASP.NET MVC templates to style and build up apps and focus on tenant topics.

Creating the tenant app

A tenant app is an invoice recording application.

To brand the tenant, we record tenant name in the app settings inside the web.config file:

<add key="TenantName" value="{put_your_tenant_name}" />

To simplify this, we “brand” the application that stores the tenant name in the main layout file where the application name is displayed.

The application content is represented by an Invoices page where we record data with a CRUD process. The entry for the Invoices page is in the Navigation bar:

<ul class="nav navbar-nav">
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("Invoices", "Index", "Invoices")</li>
        <!-- other code omitted -->

First, we need to define a model for the application in the models folder. As we need to store data in an Azure SQL database, we can use entity framework to create the model from an empty code. However, first we use the following code:

public class InvoicesModel : DbContext
    {
        public InvoicesModel()
            : base("name=InvoicesModel")
        {
        }

        public virtual DbSet<Invoice> Invoices { get; set; }
    }

As we can see, data will be accessed by a SQL database that is referenced by a connectionString in the web.config file:

<add name="InvoicesModel" connectionString="data 
    source=(LocalDb)MSSQLLocalDB;initial 
    catalog=Tenant.Web.Models.InvoicesModel;integrated 
    security=True;MultipleActiveResultSets=True;
    App=EntityFramework" providerName="System.Data.SqlClient" 
    /></connectionStrings>

This model class is just for demo purposes:

public class Invoice
    {
        public int InvoiceId { get; set; }
        public int Number { get; set; }
        public DateTime Date { get; set; }
        public string Customer { get; set; }
        public decimal Amount { get; set; }
        public DateTime DueDate { get; set; }
    }

After this, we try to compile the project to check whether we have not made any mistake. We can now scaffold this model into an MVC controller so that we can have a simple but working app skeleton.

Creating the portal app

We now need to create the portal app starting from the MVC default template. Its registration workflow is useful for the creation of our tenant registration. In particular, we utilize user registration as the tenant registration. The main information acquires the tenant name and triggers tenant deployment. We need to make two changes on the UI.

First, in the RegisterViewModel defined under the Models folder, we add a TenantName property to the AccountViewModels.cs file:

public class RegisterViewModel
    {
        [Required]
        [Display(Name = "Tenant Name")]
        public string TenantName { get; set; }

        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }

        // other code omitted
    }

In the Register.cshtml view page under ViewsAccount folder, we add an input box:

@using (Html.BeginForm("Register", "Account", FormMethod.Post,
  new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.TenantName, new { @class = "col-md-2 
          control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.TenantName, new { @class = 
              "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 
          control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-
              control" })
        </div>
    </div>
    <!-- other code omitted -->
}

Portal application can be great to allow the tenant owner to manage its own tenant, configuring or handling subscription-related tasks to the supplier company.

Deploying the portal application

Before tenant deployment, we need to deploy the portal itself.

MyAccountant is a complex solution made up of multiple Azure services, which needs to be deployed together. First, we need to create an Azure Resource Group to collect all the services:

Mastering Cloud Development using Microsoft Azure

As we already discussed earlier, all data from different tenants, including the portal itself, need to be contained inside distinct Azure SQL databases. Every user will have their own DB as a personal service, which they don’t use frequently. It can be a waste of money assigning a reserved quantity of Database Transaction Units (DTUs) to a single database. We can invest on a pool of DTUs that should be shared among all SQL database instances.

We begin by creating an SQL Server service from the portal:

Mastering Cloud Development using Microsoft Azure

We need to create a pool of DTUs, which are shared among databases, and configure the pricing tier, which defines the maximum resources allocation per DB:

Mastering Cloud Development using Microsoft Azure

The first database that we need to manually deploy is the portal database, where users will register as tenants. From the MyAccountantPool blade, we can create a new database that will be immediately associated to the pool:

Mastering Cloud Development using Microsoft Azure

From the database blade, we read the connection:

Mastering Cloud Development using Microsoft Azure

We use this connection string to configure the portal app in web.config:

<connectionStrings>
    <add name="DefaultConnection" connectionString="Server=tcp: 
      {portal_db}.database.windows.net,1433;Data 
      Source={portal_db}; .database.windows.net;Initial 
      Catalog=Portal;Persist Security Info=False;User 
      ID={your_username};Password={your_password};
      Pooling=False;MultipleActiveResultSets=False;Encrypt=True;
      TrustServerCertificate=False;Connection Timeout=30;"
    providerName="System.Data.SqlClient" />
  </connectionStrings>

We need to create a shared resource for the Web. In this case, we need to create an App Service Plan where we’ll host portal and tenants apps. The initial size is not a problem because we can decide to scale up or scale out the solution at any time (in this case, only when application is able to scale out—we don’t handle this scenario here).

Mastering Cloud Development using Microsoft Azure

Then, we need to create portal web app that will be associated with the service plan that we just created:

Mastering Cloud Development using Microsoft Azure

The portal can be deployed from Visual Studio to the Azure subscription by right-clicking on the project root in Solution Explorer and selecting Microsoft Azure Web App from Publish. After deployment, the portal is up and running:

Mastering Cloud Development using Microsoft Azure

Deploy the tenant app

After tenant registration from the portal, we need to deploy tenant itself, which is made up of the following:

  • The app itself that is considered as the artifact that has to be deployed
  • A web app that runs the app, hosted on the already defined web app plan
  • The Azure SQL database that contains data inside the elastic pool
  • The connection string that connect database to the web app in the web.config file

It’s a complex activity because it involves many different resources and different kinds of tasks from deployment to configuration. For this purpose, we have the Azure Resource Group project in Visual Studio, where we can configure web app deployment and configuration via Azure Resource Manager templates. This project will be called Tenant.Deploy, and we choose a blank template to do this.

In the azuredeploy.json file, we can type a template such as https://github.com/marcoparenzan/CreateMultitenantAppsInAzure/blob/master/Tenant.Deploy/Templates/azuredeploy.json. This template is quite complex. Remember that in the SQL connection string, the username and password should be provided inside the template. We need to reference the Tenant.Web project from the deployment project because we need to deploy tenant artifacts (the project bits).

To support deployment, we need to create an Azure Storage Account back to the Azure portal:

Mastering Cloud Development using Microsoft Azure

To understand how it works, we can manually run a deployment directly from Visual Studio by right-clicking on Deployment project from Solution Explorer and selecting Deploy. When we deploy a “sample” tenant, the first dialog will appear.

You can connect to the Azure subscription, selecting an existing resource group or creating a new one and the template that describes the deployment composition. The template requires the following parameters from Edit Parameters window:

  • The tenant name
  • The artifact location and SAS token that are automatically added having selected the Azure Storage account from the previous dialog

Now, via the included Deploy-AzureResourceGroup.ps1 PowerShell file, Azure resources are deployed. The artifact is copied with AzCopy.exe command to the Azure storage in the Tenant.Web container as a package.zip file and the resource manager starts allocating resources.

We can see that tenant is deployed in the following screenshot:

Mastering Cloud Development using Microsoft Azure

Automating the tenant deployment process

Now, in order to complete our solution, we need to invoke this deployment process from the portal application during a registration process call in ASP.NET MVC controls. For the purpose of this article, we will just invoke the execution without defining a production-quality deployment process.

We can use the following checklist before proceeding:

  • We already have an Azure Resource Manager template that deploys the tenant app customized for the user
  • Deployment is made with a PowerShell script in the Visual Studio deployment project
  • A new registered user for our application does not have an Azure account; we, as service publisher, need to offer a dedicated Azure account with our credentials to deploy the new tenants

Azure offers many different ways to interact with an Azure subscription:

For our needs, this means integrating in our application. We can make these considerations:

  • We need to reuse the same ARM template that we defined
  • We can reuse PowerShell experience, but we can also use our experience as .NET, REST, or other platform developers
  • Authentication is the real discriminator in our solution: the user is not an Azure subscription user and we don’t want to make a constraint on this

Interacting with Azure REST API, which is the API on which every other solution depends, requires that all invocations need to be authenticated to the Azure Active Directory of the subscription tenant. We already mentioned that the user is not a subscription-authenticated user.

Therefore, we need an unattended authentication to our Azure API subscription using a dedicated user for this purpose, encapsulated into a component that is executed by the ASP.NET MVC application in a secure manner to make the tenant deployment.

The only environment that offers an out-of-the box solution for our needs (so that we need to write less code) is the Azure Automation Service.

Before proceeding, we create a dedicated user for this purpose. Therefore, for security reasons, we can disable a specific user at any time. You should take note of two things:

  • Never use the credentials that you used to register Azure subscription in a production environment!
  • For automation implementation, you need a Azure AD tenant user, so you cannot use Microsoft accounts (Live or Hotmail).

To create the user, we need to go to the classic portal, as Azure Active Directory has no equivalent management UI in the new portal. We need to select the tenant directory, that is, the one in the new portal that is visible in the upper right corner. From the classic portal, go to to Azure Active Directory and select the tenant. Click on Add User and type in a new username:

Mastering Cloud Development using Microsoft Azure

Mastering Cloud Development using Microsoft Azure

Mastering Cloud Development using Microsoft Azure

Then, go to Administrator Management in the Setting tab of the portal because we need to define the user as a co-administrator in the subscription that we need to use for deployment.

Now, with the temporary password, we need to log in manually to https://portal.azure.com/ (open the browser in private mode) with these credentials because we need to change the password, as it is generated as “expired”.

We are now ready to proceed. Back in the new portal, we select a new Azure Automation account:

Mastering Cloud Development using Microsoft Azure

The first thing that we need to do inside the account is create a credential asset to store the newly-created AAD credentials and use the inside PowerShell scripts to log on in Azure:

Mastering Cloud Development using Microsoft Azure

We can now create a runbook, which is an automation task that can be expressed in different ways:

  • Graphical
  • PowerShell

We choose the second one:

Mastering Cloud Development using Microsoft Azure

As we can edit it directly from portal, we can write a PowerShell script for our purposes. This is an adaptation from the one that we used in a standard way in the deployment project inside Visual Studio. The difference is that it is runable inside a runbook and Azure, and it uses already deployed artifacts that are already in the Azure Storage account that we created before.

Before proceeding, we need of two IDs from our subscription:

  • The subscription ID
  • The tenant ID

These two parameters can be discovered with PowerShell because we can perform Login-AzureRmAccount. Run it through the command line and copy them from the output:

Mastering Cloud Development using Microsoft Azure

The following code is not production quality (needs some optimization) but for demo purposes:

param ( 
    $WebhookData,
    $TenantName
)
# If runbook was called from Webhook, WebhookData will not be null.
if ($WebhookData -ne $null) {
    $Body = ConvertFrom-Json -InputObject $WebhookData.RequestBody
    $TenantName = $Body.TenantName
}

# Authenticate to Azure resources retrieving the credential asset
$Credentials = Get-AutomationPSCredential -Name "myaccountant"
$subscriptionId = '{your subscriptionId}'
$tenantId = '{your tenantId}'
Login-AzureRmAccount -Credential $Credentials -SubscriptionId $subscriptionId -TenantId $tenantId

$artifactsLocation = 
  'https://myaccountant.blob.core.windows.net/
  myaccountant-stageartifacts'
$ResourceGroupName = 'MyAccountant'

# generate a temporary StorageSasToken (in a SecureString form) to give ARM template the access to the templatea
artifacts$StorageAccountName = 'myaccountant'
$StorageContainer = 'myaccountant-stageartifacts'
$StorageAccountKey = (Get-AzureRmStorageAccountKey -
  ResourceGroupName $ResourceGroupName -Name 
  $StorageAccountName).Key1
$StorageAccountContext = (Get-AzureRmStorageAccount -
  ResourceGroupName $ResourceGroupName -Name 
  $StorageAccountName).Context
$StorageSasToken = New-AzureStorageContainerSASToken -Container 
  $StorageContainer -Context $StorageAccountContext -Permission r 
  -ExpiryTime (Get-Date).AddHours(4)
$SecureStorageSasToken = ConvertTo-SecureString $StorageSasToken -AsPlainText -Force

#prepare parameters for the template
$ParameterObject = New-Object -TypeName Hashtable
$ParameterObject['TenantName'] = $TenantName
$ParameterObject['_artifactsLocation'] = $artifactsLocation
$ParameterObject['_artifactsLocationSasToken'] = 
  $SecureStorageSasToken

$deploymentName = 'MyAccountant' + '-' + $TenantName + '-'+ 
  ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')
$templateLocation = $artifactsLocation + 
  '/Tenant.Deploy/Templates/azuredeploy.json' + $StorageSasToken

# execute
New-AzureRmResourceGroupDeployment -Name $deploymentName `
  -ResourceGroupName $ResourceGroupName `
  -TemplateFile $templateLocation `
  @ParameterObject `
  -Force -Verbose

The script is executable in the Test pane, but for production purposes, it needs to be deployed with the Publish button.

Now, we need to execute this runbook from outside ASP.NET MVC portal that we already created. We can use Webhooks for this purpose. Webhooks are user-defined HTTP callbacks that are usually triggered by some event. In our case, this is new tenant registration. As they use HTTP, they can be integrated into web services without adding new infrastructure.

Runbooks can directly be exposed as a Webhooks that provides HTTP endpoint natively without the need to provide one by ourself. We need to remember some things:

  • Webhooks are public with a shared secret in the URL, so it is “secure” if we don’t share it
  • As a shared secret, it expires, so we need to handle Webhook update in the service lifecycle
  • As a shared secret if more users are needed, more Webhooks are needed, as the URL is the only way to recognize who invoked it (again, don’t share Webhooks)
  • Copy the URL at this stage as it is not possible to recover it but it needs to be deleted and generate a new one
  • Write it directly in portal web.config app settings:
    <add key="DeplyNewTenantWebHook" 
      value="https://s2events.azure-
      automation.net/webhooks?token={your_token}"/>
    

We can set some default parameters if needed, then we can create it.

To invoke the Webhook, we use System.Net.HttpClient to create a POST request, placing a JSON object containing TenantName in the body:

           var requestBody = new {
                TenantName = model.TenantName
           };

           var httpClient = new HttpClient();
           var responseMessage =
                await httpClient.PostAsync(
                     ConfigurationManager.AppSettings
                       ["DeplyNewTenantWebHook"],
                     new StringContent(JsonConvert.SerializeObject
                       (requestBody))
                );

This code is used to customize the registration process in AccountController:

public async Task<ActionResult> Register(RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser { UserName = 
                  model.Email, Email = model.Email };
                var result = await UserManager.CreateAsync(user, 
                  model.Password);
                if (result.Succeeded)
                {
                    await SignInManager.SignInAsync(user, 
                      isPersistent:false, rememberBrowser:false);

                    // handle webhook invocation here

                    return RedirectToAction("Index", "Home");
                }
                AddErrors(result);
            }

The responseMessage is again a JSON object that contains JobId that we can use to programmatically access the executed job.

Conclusion

There are a lot of things that can be done with the set of topics that we covered in this article. These are a few of them:

  • We can write better .NET code for multitenant apps
  • We can authenticate users on with the Azure Active Directory service
  • We can leverage deployment tasks with Azure Service Bus messaging
  • We can create more interaction and feedback during tenant deployment
  • We can learn how to customize ARM templates to deploy other Azure Storage services, such as DocumentDB, Azure Storage, and Azure Search
  • We can handle more PowerShell for the Azure Management tasks

Summary

Azure can change the way we write our solutions, giving us a set of new patterns and powerful services to develop with. In particular, we learned how to think about multitenant apps to ensure confidentiality to the users. We looked at deploying ASP.NET web apps in app services and providing computing resources with App Services Plans. We looked at how to deploy SQL in Azure SQL databases and computing resources with elastic pool. We declared a deployment script with Azure Resource Manager, Azure Resource Template with Visual Studio cloud deployment projects, and automated ARM PowerShell script execution with Azure Automation and runbooks.

The content we looked at in the earlier section will be content for future articles.

Code can be found on GitHub at https://github.com/marcoparenzan/CreateMultitenantAppsInAzure.

Have fun!

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here