Accessing Connection String values in Azure Function V1 and V2

Connection string is a good way to store database or source system connection string. Currently Azure Function provides four different types on connection strings as below.

  1. MySql- Conection string for MySql database
  2. SQLServer- Connection strings for SQL server
  3. SQLAzure – Connection strings for SQL Azure
  4. Custom—Any other types of connection string excluding above

The way for accessing Connection string is different in V1 and V2 as V2 is does not support configuration manager.

Accessing Connection string in Azure function V1

Below are the steps for accessing connection string in azure function v1 .

  1. Add nuget package “Configuration.ConfigurationManager
  2. Add the namespace “System.Configuration
  3. Then access connection string as below
var connectionString = ConfigurationManager.ConnectionStrings[“CRM_CONNECTION_STRING”].ConnectionString;

While running the code from azure app service create the connection as custom connection string

The sample code could be found on GitHub

 Accessing Connection string in Azure function V2

Unlike azure function 1.x azure function 2.x does not support configuration manager. So, the process for accessing the connection string is different. Below are the steps for that

  1. Add the namespace “Extensions.Configuration
  2. Add a third parameter in the function Run method of type ExecutionContext
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, “get”, “post”, Route = null)]HttpRequest req, ILogger log, ExecutionContext context)
  1. Add the following code in the beginning of the function
var config = new ConfigurationBuilder()

.SetBasePath(context.FunctionAppDirectory)

.AddJsonFile(“local.settings.json”, optional: true, reloadOnChange: true)

.AddEnvironmentVariables()

.Build();

For a local run this code will get application settings and connection string from local.settings.json and for a deployment this code this take the environment variables from appsettings and connection string from the app service.

  1. Access Connection String as below
var connectionString = config.GetConnectionString(“CRM_CONNECTION_STRING”);
  1. Access app settings as below
var setting1 = config[“Settings1”];

The sample code can be found on GitHub

 

 

 

 

 

 

 

Advertisements

Accessing Key Vault in Azure Function

Azure Key Vault is a secure storage for keys connection strings and password. Azure Key Vault helps safeguard cryptographic keys and secrets used by cloud applications and services. By using Key Vault, you can encrypt keys and secrets (such as authentication keys, storage account keys, data encryption keys, .PFX files, and passwords) using keys protected by hardware security modules (HSMs).

Key Vault streamlines the key management process and enables you to maintain control of keys that access and encrypt your data. Developers can create keys for development and testing in minutes, and then seamlessly migrate them to production keys. Security administrators can grant (and revoke) permission to keys, as needed. More details about this can be found on https://docs.microsoft.com/en-us/azure/key-vault/key-vault-whatis

Below I will be discussing what all are the steps to access Key vault from Azure function.

Steps to Access key vault

  • Create an Azure Key vault as below

1

  • Create a key vault secret as below

2

3

  • Write an Azure Function code as below
[FunctionName(“GetKeyVaultValues”)]

public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, “get”, “post”, Route = null)]HttpRequestMessage req, TraceWriter log)

{

log.Info(“C# HTTP trigger function processed a request.”);

string linkKeyVaultUrl = $”https://keyvaultaccess.vault.azure.net/secrets/&#8221;;

string keyvaultKey = $”KeyVaultKey”;

var secretURL = linkKeyVaultUrl + keyvaultKey;

 

var azureServiceTokenProvider = new AzureServiceTokenProvider();

var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));

try

{

var clientIdRecord = await kvClient.GetSecretAsync(secretURL).ConfigureAwait(false);

string KeyvaultValue = clientIdRecord.Value;

return req.CreateErrorResponse(HttpStatusCode.OK, “Key vault secret value is  :  ” + KeyvaultValue);

}

catch (System.Exception ex)

{

 

return req.CreateResponse(HttpStatusCode.BadRequest, “Key vault value request is not successfull”);

}

}

  • The nuget packages used in this code are as follows
    • Microsoft.Azure.KeyVault
    • Microsoft.Azure.Services.AppAuthentication
  • publish the Azure function as below

4

  • Go to the platform feature for the application and set the managed identity as On

5

  • Go to the Azure Key Vault
  • Add the application to access policy as shown below.

6

 

7

  • Set the Key and Secret permission as below

8

Now you can go to “Postman” and call the method to get the key vault value and use wherever required

9

 

 

 

 

IP ranges for Dynamics 365

IP whitelisting (Range) for Dynamics 365.

In one of our recent project we need to call a web service from Workflow and Action from Dynamics 365 online (9.x) . The web service publisher will allow only request from specific IP ranges due to security principal of the organisation.

To Achieve this IP range there could be multiple approach but I will explain the below two

  1. Add Dynamics IP range to the allowed IP range for the Web Service Publisher.
  2. Use Azure API manager or some other static IP provider in between Dynamics 365 and the Web service.

In the current blog I will explain the Dynamics 365 IP ranges. I will explain the Azure API manager in future blog.

Add Dynamics IP range to the allowed IP range for the webservice

The IP range for dynamics CRM can be found on https://support.microsoft.com/en-us/help/2728473/microsoft-dynamics-crm-online-ip-address-ranges . this link has the exhaustive list for Dynamics CRM for each CRM region. For Oceania region the list is around 10 IP ranges.

But I found that the Request to the web service is going from 13.70.86.61 which is not listed in the in the IP range for Oceania for CRM, but this is within a range Azure IP range for Oceania region.

So, I understood with 9.x version as dynamics is using Azure infrastructure so request is going from Azure IP range. Up to CRM 8.x version the request seems to go from Dynamics IP range(Listed previously) . To get double sure I raised a ticket with Microsoft.

Based on the reply from Microsoft, Dynamics 365 does not have any static IP range furthermore with 9.x and higher as the Azure infra has been used. The IP that the request will go from Azure IP range for this region.

The Azure IP range all region can be downloaded from https://www.microsoft.com/en-us/download/details.aspx?id=41653

Below is the reply from Microsoft for the ticket.

Ip Range

Conclusion

So, If you have the requirement for IP range from currently use all the Azure IP range for the region. This is not a clean solution as this IP range could be changed by Microsoft anytime. So, Using API manager and wrapper service will be a cleaner solution but this include extra cost for customer in terms of development and Azure hosting.

I wish Microsoft will come with a more specific IP ranges for Dynamics Application.

I will explain the API manager and Wrapper service approach in future blog.

 

At this time Microsoft Dynamics 365 requires version 4.6.2 of the .NET Framework for plugin assemblies.

Today I tried to import a solution from Dev environment to QA and I found Solution import failed. The error is “At this time Microsoft Dynamics 365 requires version 4.6.2 of the .NET Framework for plugin assemblies. Rebuild this assembly using .NET Framework version 4.6.2 and try again.”

Over Checking the assembly, I found that my Plug-in assembly is in .NET framework version 4.6.2. So, the error is a bit misleading. I am working in this project for three months and  .NET framework version we are using is always 4.6.2. This issue is happening only after 10th may 2018 before this it use to work fine.

Resolution

I have downgraded the .NET framework to 4.5.2 only for Plug in and rebuilt the solution and upgraded the assembly in Dev Environment. After that I exported and imported the solution from Dev to QA. This time it worked without any issue.

Root cause of this issue could be some inconsistency in patch deployment in Dynamics instance. The error seems to be misleading also.

Conclusion

So, until we get new version we need to keep on using .NET framework version 4.5.2 for plug in and workflow development in Dynamics 365(v 9.x).

MultiSelect Option set does not show in Dynamics portal

MultiSelect Option set is a great feature in Dynamics 365. From a portal prospective this filed has limitation.

  1. Multiselect Fields does not appear in Entity List and It shows an error.
  2. Multiselect field does not appear in Entity Form. Entity form just skips the field
  3. Multiselect field does not appear in Web Forms Steps. Entity form step just skips the field
  4. I am yet to check how this filed will behave in a Liquid template- I will write another blog in that once I get time

In the below Example how, portal will behave in Entity Form, Entity List and Web Form Steps

I have Created a multiselect field as Industry in Lead Entity with value IT, Networking, HR, Finance, Back Office as shown below.

Image1

I have added a record in Lead Entity as shown below with industry selected as IT and Networking.

Image2

For Entity List

Entity list throws an error if you add multiselect field on the view. It we remove the fields from entity list the grid works fine

Image3

For Entity Form

Entity form just skips the field if we added the field in Entity Form. In the below example although the entity form contains the field but portal simply skips the field

Image4

Image5

 

Web Form Step

In the Web form we can see as below the portal simple skips the multiselect option set although that is present in the form

Image6

 

 

Dynamics Key failed during Solution import and does not throw any error

Dynamics Key failed during Solution import without any error

In my recent project I found an issue when I was importing the solution from Dev to QA. The solution import was successful and there was no error. But, I found that keys of the solution is on Pending state as shown below

Image1

In this system keys in the system will not work . I tried to reactivate the keys and got the below error. I have re imported the Solution but  and published the solution, but the issue persist. When the issue persist

Image2

Iamge3

When I click on the “Create Index ”, it shows me the failed job

Iamge4

Resolution

Following are the steps for resolving the issue

Go to Setting -> Systems -> System Jobs. Filter the jobs based on “Failed” State.  I found that lots of system job is in failed state as shown below.

Iamge 5

Select all the jobs and “Delete” them.

After deletion is completed, Go to the entity inside the solution  and to the key where it failed. Click on the “Reactivate Key” as shown below

Image 6

This Time key generation was successful and the status of the key is Active as shown below and system works as expected.

Iamge 7

Adding Relationship Programatically In MSCRM

I faced a Peculiar Behaviour in MSCRM. If I am trying to add Relationship through UI Its not allowing me and CRM is getting Time out.

Then I have increase the Time out of CRM but no luck

So I have started writing Codes for Creating Relationship

For Creating Relation ship in CRM We need to use Metadata Service

There are two types of Relationship We can create

1)One To Many and

2) Many to Many

Creating One to Many Relationship

public static void createOneToManyTest(MetadataService metadataService)
{
try
{

OneToManyMetadata oneToMany = new OneToManyMetadata();

oneToMany.SchemaName = “new_relationship_name”;

oneToMany.ReferencedEntity = “systemuser”;

//Side A

oneToMany.ReferencedAttribute = “systemuserid”;

oneToMany.ReferencingEntity = “incident”;

oneToMany.ReferencingAttribute = “incidentid”;

oneToMany.RelationshipType = EntityRelationshipType.OneToMany;

oneToMany.SchemaName = “new_systemuser_incident_ResolvedBy”;

LocLabel locLabel = new LocLabel();
locLabel.Label = “Resolved By”;
locLabel.LanguageCode = new CrmNumber(1033);

LocLabel[] lstLable = new LocLabel[1];
lstLable[0] = locLabel;

oneToMany.AssociatedMenuGroup = new CrmAssociatedMenuGroup(AssociatedMenuGroup.Details);
oneToMany.AssociatedMenuLabel = new CrmLabel(lstLable, new LocLabel(“Resolved By”, new CrmNumber(1033)));
oneToMany.AssociatedMenuOrder = new CrmNumber(15001);

oneToMany.AssociatedMenuBehavior = new CrmAssociatedMenuBehavior(AssociatedMenuBehavior.UseLabel);

oneToMany.CascadeAssign = new CrmCascadeType(CascadeType.NoCascade);
oneToMany.CascadeDelete = new CrmCascadeType(CascadeType.RemoveLink);
oneToMany.CascadeMerge = new CrmCascadeType(CascadeType.NoCascade);
oneToMany.CascadeReparent = new CrmCascadeType(CascadeType.NoCascade);
oneToMany.CascadeShare = new CrmCascadeType(CascadeType.NoCascade);
oneToMany.CascadeUnshare = new CrmCascadeType(CascadeType.NoCascade);

oneToMany.IsCustomRelationship = new CrmBoolean(true);
oneToMany.IsValidForAdvancedFind = new CrmBoolean(true);
oneToMany.SecurityType = SecurityTypes.Pointer;

CreateOneToManyRequest oneToManyRequest = new CreateOneToManyRequest();

LookupAttributeMetadata lkpMeta = new LookupAttributeMetadata();
lkpMeta.EntityLogicalName = EntityName.incident.ToString();
lkpMeta.DisplayName = new CrmLabel(lstLable, new LocLabel(“Resolved By”, new CrmNumber(1033)));
lkpMeta.LogicalName = “ResolvedBy”;
lkpMeta.RequiredLevel = new CrmAttributeRequiredLevel(AttributeRequiredLevel.None);
lkpMeta.SchemaName = “new_ResolvedByid”;
oneToManyRequest.Lookup = lkpMeta;
oneToManyRequest.OneToManyRelationship = oneToMany;

metadataService.Execute(oneToManyRequest);
}
catch (SoapException e)
{

Console.WriteLine(“Error Occured:” + e.Detail.ToString());
}
catch (Exception Ex)
{
Console.WriteLine(“Error Occured:” + Ex.StackTrace.ToString());
}

}

Creating Many to Many RelationShip

public static void createManyToManyTest(MetadataService metadataService)
{

ManyToManyMetadata manyToMany = new ManyToManyMetadata();

manyToMany.SchemaName = “new_relationship_name”;

manyToMany.IntersectEntityName = “new_intersect_name”;

//Side A

manyToMany.Entity1LogicalName = “account”;

manyToMany.Entity1AssociatedMenuBehavior = new CrmAssociatedMenuBehavior();

manyToMany.Entity1AssociatedMenuBehavior.Value = AssociatedMenuBehavior.UseLabel;

manyToMany.Entity1AssociatedMenuGroup = new CrmAssociatedMenuGroup();

manyToMany.Entity1AssociatedMenuGroup.Value = AssociatedMenuGroup.Details;
LocLabel locLabel = new LocLabel();
locLabel.Label = “my custom label”;
locLabel.LanguageCode = new CrmNumber(1033);

LocLabel[] lstLable = new LocLabel[1];
lstLable[0] = locLabel;

manyToMany.Entity1AssociatedMenuLabel = new CrmLabel(lstLable, new LocLabel(“my custom lable”,new CrmNumber(1033)));
manyToMany.Entity1AssociatedMenuOrder = new CrmNumber();

manyToMany.Entity1AssociatedMenuOrder.Value = 15001;

//Side B

manyToMany.Entity2LogicalName = “contact”;

manyToMany.Entity2AssociatedMenuBehavior = new CrmAssociatedMenuBehavior();

manyToMany.Entity2AssociatedMenuBehavior.Value = AssociatedMenuBehavior.UseLabel;

manyToMany.Entity2AssociatedMenuGroup = new CrmAssociatedMenuGroup();

manyToMany.Entity2AssociatedMenuGroup.Value = AssociatedMenuGroup.Details;

manyToMany.Entity2AssociatedMenuLabel = new CrmLabel(lstLable, new LocLabel(“my custom lable”, new CrmNumber(1033)));
manyToMany.Entity2AssociatedMenuOrder = new CrmNumber();

manyToMany.Entity2AssociatedMenuOrder.Value = 15001;

CreateManyToManyRequest manyToManyRequest = new CreateManyToManyRequest();

manyToManyRequest.IntersectEntitySchemaName = manyToMany.IntersectEntityName;

manyToManyRequest.ManyToManyRelationship = manyToMany;

metadataService.Execute(manyToManyRequest);

}