Friday, 22 December 2023

How to Connect to Azure B2C using C#

 Below is the code related to how to connect to Azure AD B2C using B2CGraphClient. 


    1. Get Access Token using following approach : 

        

 this.clientId = clientId;

            this.clientSecret = clientSecret;

            this.tenant = tenant;


            // The AuthenticationContext is ADAL's primary class, in which you indicate the direcotry to use.

            this.authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);


            // The ClientCredential is where you pass in your client_id and client_secret, which are 

            // provided to Azure AD in order to receive an access_token using the app's identity.

            this.credential = new ClientCredential(clientId, clientSecret);


2.  Send Graph Request using below code : 


 // First, use ADAL to acquire a token using the app's identity (the credential)

            // The first parameter is the resource we want an access_token for; in this case, the Graph API.


            Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult result = await authContext.AcquireTokenAsync("https://graph.windows.net", credential);

            //AuthenticationResult result = authContext.AcquireToken("https://graph.windows.net", credential);


            // For B2C user managment, be sure to use the 1.6 Graph API version.

            HttpClient http = new HttpClient();

            string url = "https://graph.windows.net/" + tenant + api + "?" + Globals.aadGraphVersion;

            if (!string.IsNullOrEmpty(query))

            {

                url += "&" + query;

            }


            Console.ForegroundColor = ConsoleColor.Cyan;


            // Append the access token for the Graph API to the Authorization header of the request, using the Bearer scheme.

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            HttpResponseMessage response = await http.SendAsync(request);


            if (!response.IsSuccessStatusCode)

            {

                string error = await response.Content.ReadAsStringAsync();

                object formatted = JsonConvert.DeserializeObject(error);

                throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));

            }


            Console.ForegroundColor = ConsoleColor.Green;


            await response.Content.ReadAsStringAsync();

How To Retrieve More than 5K Records in MS CRM

  Please find below sample code to check how to retrieve more than 5K Records in MS CRM using C# code :


Problem Statement : Query Expression can’t fetch more than 5k Records in Ms CRM

Create a Paging Programmatically and Execute data present in first page. Then proceed with other pages until execution completes for all Number of Records in Production.

C# Code to achieve Solution  using Query Expression :

  string countryLocale = "%" + countryId + "%";

                QueryExpression query = new QueryExpression("customerproductwarranty");

               

                // Or retrieve All Columns

                query.ColumnSet = new ColumnSet(true);

                ConditionExpression condition1 = new ConditionExpression();

                condition1.AttributeName = "locale";

                condition1.Operator = ConditionOperator.Like;

                condition1.Values.Add(countryLocale);

                FilterExpression filter1 = new FilterExpression();

                filter1.Conditions.Add(condition1);

                query.Criteria.AddFilter(filter1);

 

                query.PageInfo = new PagingInfo();

                query.PageInfo.Count = 1000;

                query.PageInfo.PageNumber = 1;

                query.PageInfo.ReturnTotalRecordCount = true;

                EntityCollection myContacts = service.RetrieveMultiple(query);

 

                Console.WriteLine("First set of Records retrieved are :" + myContacts.Entities.Count);

                objErrorLog.WriteErrorLog("First set of Records retrieved are :"+myContacts.Entities.Count);

                var queryResult = service.RetrieveMultiple(query);

                if (queryResult.Entities.Count > 0)

                {

                    count = customerWarrantyDateFormatFix(queryResult , i , service);

                    i += count;

                }

               while (myContacts.MoreRecords)

                {

                    query.PageInfo.PageNumber += 1;

                    query.PageInfo.PagingCookie = myContacts.PagingCookie;

                    myContacts = service.RetrieveMultiple(query);

                    Console.WriteLine("More Records retrieved are : " + myContacts.Entities.Count);

                    objErrorLog.WriteErrorLog("More Records retrieved are :" + myContacts.Entities.Count);

                    //Add to the collection

                    var queryResult1 = service.RetrieveMultiple(query);

                    if (queryResult1.Entities.Count > 0)

                    {               

                       count = customerWarrantyDateFormatFix(queryResult1 ,i, service);

                        i = count;

                    }

                                    }

             Place your functionalities inside method : customerWarrantyDateFormatFix so that Operations like Update / Get can be performed.

 

C# code to achieve using Fetch XML : 


string countryLocale = "%" + countryId + "%";

// int recordCreatedBeforeDays = createdBeforeDays;

// Define the fetch attributes.

// Set the number of records per page to retrieve.

int fetchCount = 5000;

// Initialize the page number.

int pageNumber = 1;

// Initialize the number of records.

int recordCount = 0;

// Specify the current paging cookie. For retrieving the first page, 

// pagingCookie should be null.

string pagingCookie = null;

            try

            {


String fetchRecordsfromCRMforNonAutoWarrantyCountries = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>

                      <entity name='contact'>

                       <all-attributes />

                        <order attribute='fullname' descending='false' />

                        <filter type='and'>

                          <condition attribute='locale' operator='like' value='" + countryLocale + @"' />

                          <condition attribute = 'emailaddress1' operator= 'not-null'  /> 

                          <condition attribute='modifiedon' value='" + lastXDays + @"' operator='last-x-days'/>

                        </filter>

                      </entity>

                    </fetch>";


                while (true)

                {

                    string xml = string.Empty;


                         xml = CreateXml(fetchRecordsfromCRMforNonAutoWarrantyCountries, pagingCookie, pageNumber, fetchCount);

 

                    // Excute the fetch query and get the xml result.

                    RetrieveMultipleRequest fetchRequest1 = new RetrieveMultipleRequest

                    {

                        Query = new FetchExpression(xml)

                    };


EntityCollection returnCollection = ((RetrieveMultipleResponse)service.Execute(fetchRequest1)).EntityCollection;

                    

}

                    // Check for morerecords, if it returns 1.

                    if (returnCollection.MoreRecords)

                    {

                        Console.WriteLine("\n****************\nPage number {0}\n****************", pageNumber);

                        objErrorLog.WriteErrorLog("\n****************\nPage number {0}\n****************", pageNumber.ToString());


                        // Increment the page number to retrieve the next page.

                        pageNumber++;

                        //refresh the connection

                        service = (IOrganizationService) conn.OrganizationWebProxyClient ?? (IOrganizationService) conn.OrganizationServiceProxy;

objErrorLog.WriteErrorLog("connection refreshed");

                        Console.WriteLine("Connection refreshed");

                        // Set the paging cookie to the paging cookie returned from current results.                            

                        pagingCookie = returnCollection.PagingCookie;

                    }

                    else

                    {

                        // If no more records in the result nodes, exit the loop.

                        break;

                    }

    }

            catch (Exception e)

            {

                Console.WriteLine(" No Records fetched for given Query  ");

                objErrorLog.WriteErrorLog(" No Records fetched with Exception  " + e);

            }



Azure Key Vault Connection to connect to MS CRM

 Below is the sample code regarding how to connect to Azure Key Vault to get Credentials to connect to any Ms CRM Organizations 


 // Azure Key Vault Connections 

            string keyVaultName = ConfigurationManager.AppSettings["KeyVaultName"];

            string crmUrl = ConfigurationManager.AppSettings["crmUrl"];


            string password = "";

            var kvUri = String.Format(ConfigurationManager.AppSettings["KeyVaultUrl"], keyVaultName);


            var credential = new Azure.Identity.ClientSecretCredential(ConfigurationManager.AppSettings["KeyVaultAzureTenantID"], ConfigurationManager.AppSettings["KeyVaultAzureClientID"], ConfigurationManager.AppSettings["KeyVaultAzureClientSecret"]);

            var client = new SecretClient(new Uri(kvUri), credential);


            var user = client.GetSecret(ConfigurationManager.AppSettings["KeyValueUserIDSecret"]).Value;

            userName = user.Value;


            var pwd = client.GetSecret(ConfigurationManager.AppSettings["KeyValuePasswordSecret"]).Value;

            password = pwd.Value;


            string connectionString = string.Format("AuthType=OAuth;Username={0};Password={1};Url={2};AppId=760c6b53-9d70-4fe0-8b08-2b6001f7069d;RedirectUri=app://468550f1-718b-416c-bd12-a686589aadbb;LoginPrompt=Never;RequireNewInstance = True", userName, password, crmUrl);


            var conn = new CrmServiceClient(connectionString);


IOrganizationService service;

            service = (IOrganizationService)conn.OrganizationWebProxyClient ?? (IOrganizationService)conn.OrganizationServiceProxy;


Tuesday, 2 August 2022

Dynamics Portal Web Api

     You can use the Web API to perform create, read, update, and delete operations across all Microsoft Dataverse tables from your portals pages. Below set of Operations can be performed using Portal Web Apis : 

    

  • Read records from a table
  • Create a record in a table
  • Update and delete records in a table

  • Associate and disassociate tables

Design Flow : 



Steps to enable Portal Web Api in your CRM Organization : 

1. Create Site setting for Web Api to use particular Entity as shown below :

    > Site Settings > 


2. Create another site setting for the Entity fields to use as shown below : 

                  

3. Enable Entity Permissions for the Contact by using Table permissions setting and provide add newly created  Web Role to provide access to your web api 

4. Once all the above steps are completed - you can update your contact Record from portal pages using below code with the help of Portal Web api as shown below : 

(function(webapi, $){
function safeAjax(ajaxOptions) {
var deferredAjax = $.Deferred();
shell.getTokenDeferred().done(function (token) {
// add headers for ajax
if (!ajaxOptions.headers) {
$.extend(ajaxOptions, {
headers: {
"__RequestVerificationToken": token
}
}); 
} else {
ajaxOptions.headers["__RequestVerificationToken"] = token;
}
$.ajax(ajaxOptions)
.done(function(data, textStatus, jqXHR) {
validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
}).fail(deferredAjax.reject); //ajax
}).fail(function () {
deferredAjax.rejectWith(this, arguments); // on token failure, pass the token ajax and args
});
return deferredAjax.promise();
}
webapi.safeAjax = safeAjax;
})(window.webapi = window.webapi || {}, jQuery)



// Notification component
var notificationMsg = (function() {
var $processingMsgEl = $('#processingMsg'),
_msg = 'Processing...',
_stack = 0,
_endTimeout;
return {
show: function(msg) {
$processingMsgEl.text(msg || _msg);
if (_stack === 0) {
clearTimeout(_endTimeout);
$processingMsgEl.show();
}
_stack++;
},
hide: function() {
_stack--;
if (_stack <= 0) {
_stack = 0;
clearTimeout(_endTimeout);
_endTimeout = setTimeout(function() {
$processingMsgEl.hide();
}, 500);
}
}
}
})();

//Applicaton ajax wrapper 
function appAjax(processingMsg, ajaxOptions) {
notificationMsg.show(processingMsg);
return webapi.safeAjax(ajaxOptions)
.fail(function(response) {
if (response.responseJSON) {
alert("Error: " + response.responseJSON.error.message)
} else {
alert("Error: Web API is not available... ")
}
}).always(notificationMsg.hide);
}

var userGuid = $("#adx_user_guid").val();
     appAjax('Updating...', {
      type: "PATCH",
url: "/_api/contacts("+userGuid+")",
contentType: "application/json",
data: JSON.stringify({
" Login Time": dateTime,
}),
success: function (res) {
console.log(res);
}
        });


The above code should update your contact record with Logged in user Date time. Hope it helps. 

Thank You!



Wednesday, 21 July 2021

 

Get/Post/Delete Requests to Azure AD B2C

 

                     


 

As Azure AD B2C is growing to replace Authentication methodology for Microsoft Dynamics Portals as well as other Applications , there is a growing demand to handle Get/Post/Delete Requests  to Azure AD B2C via C# .Net Code.

 

Below is the Info regarding how to make API Requests to Azure B2C using simple coding approaches and Details reg Pre-release Versions.

Dll\Namespace Required to Acquire Classes to access Microsoft Graph APIs :

1.       Microsoft.Graph

2.       Microsoft.Identity.Client

 

 

 

Above mentioned dlls contain definition for IConfidentialClientApplication which can be used to get AuthProvider as shown below :

 

     IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder

            .Create(clientId)

            .WithTenantId(tenant)

            .WithClientSecret(clientSecret)

            .Build();

 

                  ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);

Problem :

ClientCredentialProvider method present in Base  Microsoft.Identity.Client Dll and it is not completely tested and recommended by Microsoft yet as it is still in PreRelease stage and we can’t use it in Production. Also , Visual Studio doesnot recognise ClientCredentialProvider class. Hence, we can proceed with below approach which provides authContext and also helps in performing Get/Delete Requests to Azure AD B2C.

 

Solution :

Set below Configurations in App.Config :

            string clientId = ConfigurationManager.AppSettings["b2c:ClientId"];

            string clientSecret = ConfigurationManager.AppSettings["b2c:ClientSecret"];

            string tenant = ConfigurationManager.AppSettings["b2c:Tenant"];

 

      // The app registration should be configured to require access to permissions

        // sufficient for the Microsoft Graph API calls the app will be making, and

            // those permissions should be granted by a tenant administrator.

            var scopes = new string[] { "https://graph.microsoft.com/.default" };

            // Configure the MSAL client as a confidential client

            var confidentialClient = ConfidentialClientApplicationBuilder

                .Create(clientId)

                .WithAuthority($"https://login.microsoftonline.com/$tenantId/v2.0")

                .WithClientSecret(clientSecret)

                .Build();

 

            // Build the Microsoft Graph client. As the authentication provider, set an async lambda

            // which uses the MSAL client to obtain an app-only access token to Microsoft Graph,

            // and inserts this access token in the Authorization header of each API request.

            GraphServiceClient graphServiceClient =

                new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => {

 

                    // Retrieve an access token for Microsoft Graph (gets a fresh token if needed).

                    var authResult = await confidentialClient

                        .AcquireTokenForClient(scopes)

                        .ExecuteAsync();

 

                    // Add the access token in the Authorization header of the API request.

                    requestMessage.Headers.Authorization =

                        new AuthenticationHeaderValue("Bearer", authResult.AccessToken);

                })

                );

 

Make Either Get Request to Azure B2C or Delete Requests as shown below :

 

            // Make a Microsoft Graph API query

            graphServiceClient.Users[B2cObjectId/User-Id]

               .Request()

               .DeleteAsync();

        }

 

This helps us to fetch Client Id , Client Secret and Tenant Id from App Settings and Get Token as required and perform necessary operations using simple Requests.

 

 

Sunday, 17 May 2020

Azure Attachment Management & Blob Storage


Configuring Azure Attachment Management and Azure Blob Storage

1.       Download Attachment Manager from Microsoft App Source and install it in your respective Orgs.
2.       Connect to your CRM Instance
3.       Create Azure Storage as shown I pic below:

Once created, open the storage account and scroll down and open the Blobs service. Here, the admin will be able to create the containers that will contain the attachments uploaded via CRM.  Create two global containers (which will be utilised in the Azure Attachment Storage Configuration page) and then create a container for each entity that will map attachments to Azure Blob Storage.
Note: Create two (global) containers named “emailsattachmentcontainer” and “notesattachmentcontainer.” Set the “Public Access Level” of any other containers for their respective entities to “Blob,” as this will enable users to have a preview feature

4.       Now provide access to the users by navigating to Shared Access Signature in Azure portal and create SAS Token by setting Expiry DateTime as shown below :

5.       Now navigate to the Dynamics Azure Attachments and paste SAS token in Configuration window as shown below :

6.       Now enable the Entity for Azure Blob Storage and map Entity in Container window as shown below :

7.       For Email Attachments , map Email entity as shown below :

8.       Provide necessary Access to Users by assigning Security Roles which has Azure Blob Storage Settings, NotesEntityAttachment Settings permissions.


Wednesday, 6 May 2020

Bi-Directional Integration between CRM and other Dot Net Apps


Bi-Directional Integration between CRM and Dot Net App using Azure Components ( No-Code Approach)

Hi All ,

I would like to provide a way to Integrate Dot net App with CRM using Azure Components and no code approach which can be done by leveraging Azure components like MS Flow , Custom Connectors and WebHooks.

Using Custom Connectors :
A custom connector is a wrapper around a REST API (Logic Apps also supports SOAP APIs) that allows Logic Apps, Power Automate, or Power Apps to communicate with that REST or SOAP API. We can define Actions inside Custom Connectors which can be used later in Azure Functions or MS Flows to integrate with other Product ( This is One-Way Integration.)

How to Create Custom Connector & Actions:

You should have Office 365 Admin privileges in order to create a custom connector and you can create custom connector by following steps :
1.     Login in to : https://make.powerapps.com/
2.     Expand Data Icon and look for Custom Connector option as shown below :





3.     You will see below Screen , where you can create Custom Connector either from Stratch or using Open Api file or even using Postman Collection :

4.     If you proceed with PostMan collection , you can create a collection in Post Man by using POST Method and using Endpoint URL of the App which you want to Integrate to and provide Auth Headers and Body as required. In my scenario , I am using API Key in order to authenticate with the Dot Net app which I want to integrate to as shown below :






5.     Once you prepare Post Man Collection , you can use it Custom Connector as Action to perform Desired Action in the Integrated Application as required as shown below :


Also, you can verify Request page to see whether Endpoints , Headers and Body details are correct.

6.     Once Custom Connector is Created , you can use it inside MS Flow as shown below in order to perform necessary actions as Required.




Example : In my example whenever a Lead is created in CRM , I will trigger a MS Flow which in turn uses Custom Connector to create Record in another Application which I want to integrate.


Two – Way Integration :

Consider you want to create a Lead in CRM whenever lead is created in your Organization’s Dot Net Application , the best way to proceed forward is to use WebHooks ( Again – using Azure Http Web Hook component we don’t need to use any Azure Functions or use Custom Workflows to deal with Web Hooks , we can just do some small configurations which help us in receiving data from other app as shown below :

1.     Whenever an Event takes place in Dot Net app , trigger Http Web Api call to Azure Subscribed URI and use Http Web Hook component in Azure in order to receive the Http Request as shown below :


2.     Provide Subscribe URI ( Endpoint of Integrated App ) and Body as shown below :



3.     Once Web Hook is used inside MS Flow , you will receive Context which contains event details as we have passed and attribute data ( like External Identifier) which we can use inside Flow as decision maker in order to either Create\Update the record inside CRM.

Hope this helps. Happy Integrating!