This is documentation for Apprenda 7 and 8.
Documentation for older versions are also available.

Contexts and .NET Apps

Apprenda maintains various Contexts during code execution. A Context can be defined as a construct that strictly bounds the execution environment. Apprenda is a distributed application container that relies on a variety of context rules for execution. There are six Context types in Apprenda; these contexts co-exist in a loose hierarchy and that can be mapped to Apprenda’s first-class entities.

  1. SessionContext - When a User logs in to Apprenda successfully, Apprenda grants that User a “session.” This session defines the broadest execution scope that Apprenda maintains and is the primary execution driver. All other Contexts depend on the Session Context and do not execute outside of its defined boundary. Sessions are application-agnostic, and are explicitly owned by a User and the Tenant Account they belong to. The lifecycle of a session is bound to either an explicit logout or an Apprenda event such as inactive session expiration. Sessions are immutable and non-transferable.
  2. RequestContext - Each time a request targeting an application on Apprenda enters Apprenda, a Request Context is generated. Request Contexts are bound to a User/application pair. A unique Request Context is generated for each and every request, and destroyed once the request has been satisfied. Requests are immutable and cannot be altered to change how code executes.
  3. TenantContext - Tenant Context defines execution and scoping rules bound to the User’s Tenant (otherwise known as the Organization or Tenant Account that the User belongs to). Each User request generates a Request Context that is governed by a Tenant Context. Apprenda uses the Tenant Context to make various runtime decisions including, but not limited to, where to store SQL data generated by a request. Tenant Contexts can be created via the API at runtime, which transfers ownership of the request to a different Tenant. This provides a means for “Tenant impersonation.” Application code can only create Tenant Contexts for Tenants that are part of that application Provider’s set of subscribers.
  4. ProviderContext* - There are certain situations where a Provider may want code to execute in a Tenant-agnostic context. A Provider can achieve this by creating a Provider Context at runtime that, among other things, allows for certain types of access control to service calls as well as accessing a multi-tenant view on stored SQL data. This can be useful if a Provider wants to execute queries that access all Tenant data, such as generating benchmark statistics for the application.  In an addition, the Provider Context offers some methods related to enumerating customers which will work regardless of whether that Context is enabled for your application databases (please see below note for info on enabling/disabling Provider Context).
  5. UserContext - User Context captures an execution scope bound to the session owner. This convenience context acts as a gateway for interacting with the User’s Apprenda existence.
  6. SubscriptionContext - Subscription Context can be defined as a unique mapping to a User/application pair. Any queries related to the User and access to their subscribed application (such as Securables queries) are controlled and accessed via the Subscription Context.

*The Provider Context is disabled by default for guest applications. To enable or disable Provider Context utilization for individual guest applications, please see Configuring an Application with the Deployment Manifest

Through these Contexts, Providers can interact with Apprenda and control many aspects of execution. This Context system allows for a significant amount of flexibility while maintaining multi-tenancy. Of the mentioned Contexts, both Tenant and Provider Contexts are stackable and control execution flow. Context stacking allows for the runtime creation of new Contexts, maintaining a stacked history that can be unwound. By default, when a request enters Apprenda a single root Tenant Context is created. Additional Tenant and Provider Contexts can be created at runtime for impersonation scenarios:

The uppermost Context (regardless of type) defines the dominant scope. Tenant execution and isolation is governed via these Contexts across the grid. Apprenda preserves runtime-defined Context stacks seamlessly, despite executing application code across various memory spaces and hardware resources. This model allows the sharing of various resources on the fabric across multiple Tenants for the same application.

Retrieving Information about the Current Tenant

The TenantContext gives Tenants the ability to view and setup data related to their Organization via the Account Portal. This data includes User definitions, location and contact information for their Organization, Roles and Role mappings, Apprenda application subscriptions, and other pertinent data. Most of this data is available to Developers via the Apprenda API so that the data can be used in various Apprenda applications while giving Tenants centralized data management capabilities. This is a significant value add to the Tenants, and can be very valuable to Providers with multiple applications tapping into the same Tenant data system.

As described in prior sections, Apprenda differentiates between the Tenant and User that owns the current session, and the current Tenant at the top of the Context stack for a given request. Generally these are one and the same, but via Context manipulation an application can temporarily transfer request ownership to an alternate Tenant. In this manner, the Apprenda API allows the Developer to access data owned by the another Tenant. In other words, Apprenda takes impersonation into account when executing data requests. Via the API, a Provider can access:

  • General Tenant Account Profile Information (Organization's location, Organization Administrator information, etc)
  • User Information (name, e-mail, etc)
  • Contact Information

For example, imagine an Apprenda trucking logistics application that allows a Tenant to select one of their physical locations for acceptance of a delivery. Rather than maintain location information at the application level, an application can leave that to Apprenda and simply query for the current Tenant's locations at the user interface. The service layer may decide that if no location was chosen, the Tenant’s defined primary location will be used instead.
 

C# Example – Selection Box for UI(ASP.NET)

 
public void PopulateLocationControl()
{
   IList<IContactSection> sections = TenantContext.Current.GetContactSections();
   foreach (IContactSection section in sections)
   {
      locationControl.Items.Add(section.Label, section.Address);
   }
}

public void ScheduleDeliveryClicked()
{
   //The request will either contain a location
   //selected by a user that came from the control 
   //populate above, or no location.
   DeliveryRequest request = GetRequestFromForm();
   ILogisticsService proxy = new LogisticsServiceProxy();
   IDeliveryReceipt receipt = null;		
   try 
   {	
       receipt = proxy.ScheduleDelivery(request);
   }
   catch(FaultException<SchedulingConflictDetail> e)
   {
      Messages.Add(e.Detail.ConflictMessage);
   }

   return receipt;
}

C# Example – At the LogisticsService

[OperationContract]
public DeliveryReceipt ScheduleDelivery(DeliveryRequest request)
{
   if(null == request.DeliveryLocation)
   {
      ITenantProfile profile = TenantContext.Current.GetCompanyProfile();
      request.DeliveryLocation = profile.PrimaryLocation;
   }
   return request.Schedule();
}

This code relies on the Apprenda API for information regarding the Tenant’s defined locations, including the primary location. This lets the application focus on core logic rather than an auxiliary framework of common CRM-type data. The reference to TenantContext.Current returns the uppermost Tenant Context on the Apprenda Context stack. If the application has impersonated some other Tenant, all returned data will be owned by the impersonated Tenant rather than the session-owning Tenant (as demonstrated in the next section).

There are many other Tenant data API calls available to the calling application; please refer to the Apprenda API documentation for a full listing.

Using Impersonation to Manipulate Another Tenant's Data

Any code execution, including the storage and retrieval of an application's data, is governed by the current Context, which by default is based on the currently-logged-in User. This default behavior satisfies most use cases, but often it is desirable to manipulate another Tenant's data or otherwise have an application act on behalf of another Tenant. To do this, it is necessary to impersonate another Tenant. 

The following example demonstrates how to cause data to be saved under another Tenant. 

C# WCF Service Example

//Version 1: Uses default Tenant Context (derived from user making service call)
[OperationContract]
public void SaveInvoice(Invoice invoice)
{
   invoice.Validate();
   IInvoiceDAO dao = new InvoiceDAO();
   dao.Save(invoice);
}

//Version 2: Impersonates the other Tenant

[OperationContract]
public void SaveInvoice(Invoice invoice)
{
   using(TenantContext ctx = TenantContext.NewTenantContext(GetOtherTenantId())
   {
      invoice.Validate();
      IInvoiceDAO dao = new InvoiceDAO();			
      dao.Save(invoice);
   }
}

Note that for the purposes of the example above, InvoiceDAO.Save(...) will retrieve a connection using Apprenda's connection provider or the Apprenda API. This ensures that the data is appropriately stored in the application database managed by Apprenda.

Limitations and Scoping Rules when Impersonating Contexts

  • New context objects are IDisposable(); scope is controlled by the existence of the object. This means that if you create a new context object in a WCF service call, you must dispose of it before the service call returns.
  • It is highly recommended that a using block be used to prevent accidental impersonation leaks. 
  • Impersonation requests are nestable; multiple Tenant Contexts may be created for different Tenants in the same logical thread of execution. 
  • Dispose() must only be called on the uppermost Context on the stack (Calling Dispose() on any Context other than the topmost will have no effect).
  • Outbound calls to other WCF services deployed to Apprenda will maintain the full impersonation stack defined in local memory. Disposals, however, are localized and are not propagated back to callers on the service call stack.
  • Because impersonating another Tenant involves a User Context that does not properly belong to the impersonated Tenant, any action regulated by a Securable for the impersonated Tenant will not work, as there is no way to grant securable permissions to the impersonating User.
  • While impersonating another Tenant, if any User information is needed while inside the impersonation block, the User Context must be referenced prior to Tenant impersonation.

Using Impersonation to Operate on All Tenants' Data

A common use-case is to act on data under the Provider’s authority rather than under the authority of a specific Tenant. For example, imagine that a web service calculates benchmark statistics across all subscriber data for a given application. While it would be possible to use Tenant impersonation to serially process each piece of data for the statistical domain, this would prove slow and would most likely scale poorly. 

In the following example, a new ProviderContext is created, and "surronds" a database call, which causes it to operate on all of the Tenant's data for that version of the application (regardless of the database deployment model or the number of shards in use).

C# WCF Service Example

//Version 2: Impersonates the other Tenant
[OperationContract]
public MetricsDTO CalculateMetrics()
{
   using(ProviderContext.NewProviderContext())
   {
      return new InvoiceDAO().GetMetrics();
   }
}

Accessing Apprenda's Tenant Identifier in Database Results

Once in the ProviderContext scope, for non-aggregate queries, Apprenda will automatically return an additional column named tenant_id of type varbinary(85). Code executed in the scope can reference the column directly, providing for support of advanced data manipulation in an application.

SQL in Provider Context Example

Columns for Table Invoice: 
id, created_date, po_number, customer 

Query:
select * from invoice where created_date > (GETDATE() - 1)

TenantContext Result Columns:
id, created_date, po_number, customer

ProviderContext Result Columns:
id, created_date, po_number, customer, tenant_id

Note that the Provider Context has no effect when running an application locally with respect to data. This is because data queries are done using a standard SQL Server driver and tables are not enhanced with the tenant_id column by Apprenda. This needs to be taken into account separately if doing extensive work with the tenant_id column when you wish to test the application outside of Apprenda. 

Blocking Service Calls unless Provider Context Scope is Used

Aside from manipulating context, a Developer can establish WCF service call expectations with respect to authorization and context. Any WCF OperationContract can be signed with ProviderContextAttribute. If a service method is signed with this attribute using the default empty constructor, any calling client is expected to have made the call from within a ProviderContext. If the bool constructor is called with false as the value for IsFlowRequired, no call expectation is created and instead Apprenda will automatically create a ProviderContext prior to executing the service call, and disposes of the Context when the call has completed.

Storing and Using Data Later in the Request

Many times, multi-tenant applications need certain functionality, such as Tenant-level customization, as a non-invasive “aspect” to their primary business logic. From a design perspective, many architects may not want to litter primary business logic and service calls with customization or non-primary data. Apprenda allows data of this sort to be based “out of band” via the Request Context’s outer band, which is an instance of Apprenda’s ICommunicationBand. Through the outer band DataContracts, XML serializable entities and primitives can be added to the band. The data is propagated across service calls without interfering with service method signatures. Data added deep in a call stack is back-propagated to callers up the stack, allowing for bidirectional communication. There are certain scoping and data rules that are listed in the Apprenda API documentation that one should be aware of.
 
The following example stores an object called myName for use by a separate service in the same request. 

C# Example – Code in Service 1

MyDataContract contract = GetDataContract();
SessionContext.Instance.RequestContext.
               OuterBand.AddObject("myName", contract);

 

C# Example – Code in Service 2

MyDataContract contract = SessionContext.Instance.RequestContext.
                      OuterBand.GetObject<MyDataContract >("myName");