NServiceBus Azure Host Upgrade Version 7 to 8

Component: Azure Cloud Service Host
This page targets a pre-release version and is subject to change prior to the final release.

The NServiceBus Azure Host will be deprecated as of Version 9 and users are recommended to switch to self hosting for new endpoints. Upgrading existing endpoints is still supported for Version 8.

The following are the main reasons why the NServiceBus hosts are being deprecated:

  • Ease of configuration: Endpoint configuration and related APIs have been significantly improved over the last few years. Therefore self-hosting is much simpler to set up than it used to be when hosts were first introduced.

  • NuGet functionality changes: Related to the ease of configuration, NuGet no longer allows packages to add add source files and modify project files in new projects using the SDK style .csproj project definitions. This means that installing the host won't result in a runnable endpoint out of the box, which limits usefulness of supporting that feature by NServiceBus. Existing endpoints using the host can be upgraded without issues.

  • Ease and transparency of deployment: The hosts provide abstractions for service installations that configure most of the relevant settings, but still require performing additional manual steps to ensure reliability. The additional steps were sometimes accidentally skipped, causing issues after deployment. Now the installation process is more explicit and guides through service configuration in a more transparent manner.

  • Configuration transparency: The hosts used to have various profiles and roles which automatically set certain options like disabling transactions. The effect was not obvious without reading the documentation, which could result in misunderstandings and undesired, surprising effects in the system at runtime. Self-hosting comes with full, explicit, fine-grained control over all aspects of the endpoint configuration and runtime behavior.

  • Easier troubleshooting: The hosts introduce another layer of abstraction which can make troubleshooting more difficult. Some problems require deep dive into internal implementation and tend to be hard to resolve without support.

  • Better performance: Using the hosts with default, generic settings introduces certain overhead. Self-hosting typically leads to shorter startup times and less memory consumption. With self-hosting, more control over the configuration is enabled.

Migrating procedure

See the self hosting sample for details. For multi-hosting it is recommended to apply self-hosting with multiple endpoints similar to the multi hosting sample.

If process isolation is required between the endpoints it is advised to stay with the latest version of the cloud host. Process isolation will be addressed in the upgrade guides when the host is fully deprecated.

Configuration

Self hosting gives access to the same configuration options. See below for migration of host specific configuration APIs.

Custom endpoint configuration

Configuration code in IConfigureThisEndpoint.Customize can be transferred as-is to the configuration of the self-hosted endpoint.

Roles

The AsA_Worker role didn't change any configuration and can safely be ignored.

Endpoint name

The host defaults the endpoint name to the namespace of the type implementing IConfigureThisEndpoint. Pass that value to the name to the constructor of EndpointConfiguration.

Overriding endpoint name

Overriding endpoint name using the EndpointName attribute or DefineEndpointName method is no longer needed. Pass the relevant name to the constructor of EndpointConfiguration.

Executing custom code on start and stop

The host allowed custom code to run at start and stop by implementing IWantToRunWhenEndpointStartsAndStops. Since self-hosted endpoints are in full control over start and stop operations this code can be executed explicitly when starting/stopping.

Azure Configuration Source

Azure configuration source provided the capability to load configuration settings from either the configuration file or from the cloud services role environment. This logic can simply be replaced by:

8-pre NServiceBus.Hosting.Azure
var sectionName = "mySection";
var attributeName = "myAttribute";
string value;
if (SafeRoleEnvironment.IsAvailable)
{
    var key = sectionName + "." + attributeName;
    value = SafeRoleEnvironment.GetConfigurationSettingValue(key);
}
else
{
    var section = ConfigurationResolver.GetConfigurationHandler()
                      .GetSection(sectionName) as MyConfigurationSection;
    value = section.MyAttribute;
}

// return value; // value for mySection.myAttribute

Depending on whether the endpoint is hosted in a web or workerrole, the configuration file must be resolved from a different location.

8-pre NServiceBus.Hosting.Azure
static class ConfigurationResolver
{
    public static Configuration GetConfigurationHandler()
    {
        if (IsWebsite())
        {
            return WebConfigurationManager.OpenWebConfiguration(HostingEnvironment.ApplicationVirtualPath);
        }

        return ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    }

    static bool IsWebsite() => HostingEnvironment.IsHosted;
}

Role Environment Interaction

Sometimes it is usefull to host an endpoint inside the role environment (e.g. production), or outside of it (e.g. development). The role environment related code available in the cloud services SDK cannot handle this scenario though and will throw exceptions when it is used outside of the runtime. The following class can help resolve this issue by detecting whether the role environment is available.

8-pre NServiceBus.Hosting.Azure
[DebuggerNonUserCode]
static class SafeRoleEnvironment
{
    static bool isAvailable = true;
    static Type roleEnvironmentType;
    static Type roleInstanceType;
    static Type roleType;
    static Type localResourceType;

    static SafeRoleEnvironment()
    {
        try
        {
            TryLoadRoleEnvironment();
        }
        catch
        {
            isAvailable = false;
        }

    }

    public static bool IsAvailable => isAvailable;

    public static string CurrentRoleInstanceId
    {
        get
        {
            if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this property!");

            var instance = roleEnvironmentType.GetProperty("CurrentRoleInstance").GetValue(null, null);
            return (string)roleInstanceType.GetProperty("Id").GetValue(instance, null);
        }
    }

    public static string DeploymentId
    {
        get
        {
            if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this property!");

            return (string)roleEnvironmentType.GetProperty("DeploymentId").GetValue(null, null);
        }
    }
    public static string CurrentRoleName
    {
        get
        {
            if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this property!");

            var instance = roleEnvironmentType.GetProperty("CurrentRoleInstance").GetValue(null, null);
            var role = roleInstanceType.GetProperty("Role").GetValue(instance, null);
            return (string)roleType.GetProperty("Name").GetValue(role, null);
        }
    }

    public static string GetConfigurationSettingValue(string name)
    {
        if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this method!");

        return (string)roleEnvironmentType.GetMethod("GetConfigurationSettingValue").Invoke(null, new object[] { name });
    }

    public static bool TryGetConfigurationSettingValue(string name, out string setting)
    {
        if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this method!");

        setting = string.Empty;
        bool result;
        try
        {
            setting = (string)roleEnvironmentType.GetMethod("GetConfigurationSettingValue").Invoke(null, new object[] { name });
            result = !string.IsNullOrEmpty(setting);
        }
        catch
        {
            result = false;
        }

        return result;
    }

    public static void RequestRecycle()
    {
        if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this method!");

        roleEnvironmentType.GetMethod("RequestRecycle").Invoke(null, null);
    }

    public static string GetRootPath(string name)
    {
        if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this method!");

        var o = roleEnvironmentType.GetMethod("GetLocalResource").Invoke(null, new object[] { name });
        return (string)localResourceType.GetProperty("RootPath").GetValue(o, null);
    }

    public static bool TryGetRootPath(string name, out string path)
    {
        if (!IsAvailable) throw new Exception("Role environment is not available, check IsAvailable before calling this method!");

        bool result;
        path = string.Empty;

        try
        {
            path = GetRootPath(name);
            result = path != null;
        }
        catch
        {
            result = false;
        }

        return result;
    }

    static void TryLoadRoleEnvironment()
    {
        var serviceRuntimeAssembly = TryLoadServiceRuntimeAssembly();
        if (!isAvailable) return;

        TryGetRoleEnvironmentTypes(serviceRuntimeAssembly);
        if (!isAvailable) return;

        isAvailable = IsAvailableInternal();

    }

    static void TryGetRoleEnvironmentTypes(Assembly serviceRuntimeAssembly)
    {
        try
        {
            roleEnvironmentType = serviceRuntimeAssembly.GetType("Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment");
            roleInstanceType = serviceRuntimeAssembly.GetType("Microsoft.WindowsAzure.ServiceRuntime.RoleInstance");
            roleType = serviceRuntimeAssembly.GetType("Microsoft.WindowsAzure.ServiceRuntime.Role");
            localResourceType = serviceRuntimeAssembly.GetType("Microsoft.WindowsAzure.ServiceRuntime.LocalResource");
        }
        catch (ReflectionTypeLoadException)
        {
            isAvailable = false;
        }
    }

    static bool IsAvailableInternal()
    {
        try
        {
            return (bool)roleEnvironmentType.GetProperty("IsAvailable").GetValue(null, null);
        }
        catch
        {
            return false;
        }
    }

    static Assembly TryLoadServiceRuntimeAssembly()
    {
        try
        {
#pragma warning disable 618
            var ass = Assembly.LoadWithPartialName("Microsoft.WindowsAzure.ServiceRuntime");
#pragma warning restore 618
            isAvailable = ass != null;
            return ass;
        }
        catch (FileNotFoundException)
        {
            return null;
        }
    }
}

Samples

Related Articles


Last modified