Sep 23, 2022

Azure AD B2C to handle login for Portal

Recently I realized that Azure AD B2C is already playing a big role in Portal user access. So I jumped into it and wanted to learn fundamentals. I managed configure Azure AD B2C as the method of login, new registrations etc. Here I am documenting the steps.

1. Register the Portal in Azure AD B2C

Though there is a new App registration link, I started with legacy link.


Please find below the configuration details. Reply URL is needed later (i.e. A)

Once save, you will get Application ID (i.e. B)


2. Configure Sign in Policy / Criteria for Identity Provider

Go to User flows to start this and select Sign up and sign in option in resulting window.


Here, it is essential to give Email sign up as the type of method/ Identity Provider


Now we need to set user attributes and claims. There are more combinations to play around, but what I need is to just to use First Name, Surname along with Email to use to match the users, though collecting few more attributes in registration. Hence, below is my setting.


Other important thing is selecting tfp for claim representing user flow.


Now, you are ready to save and Run user flow and save the issuer link. (i.e. C) which is visible once you click the resulting hyperlink.


3. Configure the Portal

Now go to Portal management > Site Settings to enter below entries as the final step of the exercise.

Entry 1: Use Issuer Url


> Entry 2: Use redirect URL


> Entry 3: User Application ID


> Entry 4: Use Name, Surname and Email for mapping. (value: emailaddress1=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress,firstname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname,lastname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname)


> Entry 5 (Optional): If same fields are to be mapped during sign in add this entry. (value: emailaddress1=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress,firstname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname,lastname=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname)


> Entry 6: Add this entry to make sure is new Contact registers, it allows to check the Contact entry to map the email


> Entry 7


Now browse to the Portal and click Sign in. You will get new login page from Azure AD B2C! Most importantly its going to handle all the user managements for you.


If you click, you will notice all the other fields we selected in attributes and claim section would appear.

Sep 20, 2022

Calling a Child Flow

When we worked with old school Workflows, we usually use child workflows to keep the logic in one central point if need to be trigger from different situations. I glad to see same approach is possible in modern flows/ Power Automate.

Below are the simple steps to follow;

1. Create the child flow first and make sure it is On.

2. Child Flow should be a Instant type one

3.So it will start with a manual trigger with what ever the parameter we need to pass value for.


4. Importantly, it is needed to end the child flow in style, by passing back the response to parent Flow. It can be just a simple success message.


5. Now we are good to call the child flow. When you do, it presented with Parameters, so easily values can be passed. 


This sounds simple.. but learning fundamentals is not a bad thing.

Sep 16, 2022

Run an Update Plug-ins On Demand

One of the main questions asked in D365/ Dataverse jobs interviews, event today,  is differences of Plug-ins and Workflows. I used to say Plugins cannot be executed on Demand, but workflows. Its not wrong, but there is one indirect way of executing Update plugins on Demand. That's through bulk data updated.

Within the update options of the tool, you will find an option called Touch which really doesn't update the field, but it triggers other business logic bind to that operation.  (How it is done? I don't know!)


Anyway, this is an interesting option. Below are a couple of things to note;

1. This operation doesn't add anything to Audit.

2. One limitation is if there are many custom business logics (More than one Plug-ins, WFs), they all will get executed. No way of selecting what you want.

Sep 2, 2022

Adding CRUD operations to a List in a Portal

Here I am discussing one of the main customizations you may need when developing a Power Apps Portal. That is adding CRUD (Create, Read, Update and Delete) operation on items in a list. 

There are two pre-requisites to proceed with this exercise;

1. A list should have configured already.

2. Below 2 entries should have added to the Site Settings entity that gives permission to nominated entity (in my case so_office) to be accessed through API.


All my JS codes will go in Options tab of the list.


Consider below JS, where I have embedded some extra lines to a standard code of doing something for each item in the list.

Block 1 - Change some fonts styles through CSS

Block 2 - Give different color for the lines where No of Clients are greater than 100

Block 3 - Call CRUD functions by introducing 4 new buttons.

$(document).ready(function (){
$(".entitylist.entity-grid").on("loaded", function () {
$(this).children(".view-grid").find("tr").each(function (){
// do something with each row

// Block 1
$(this).css("font-family","Sans-serif");
$(this).css("font-size","10px");

// Block 2
if ($(this).find('[data-attribute="so_noofclient"]').attr("data-value")) {
    var noOfclients = $(this).find('[data-attribute="so_noofclient"]').attr("data-value");   
    if (noOfclients >100) {
    $(this).css("background-color", "#E49C9C");
    }
    else {
    $(this).css("background-color", "#F6F5F5");
    }
}

// Block 3
   if ($(this).closest('tr').attr("data-id") != null)
   {
     var recId = $(this).closest('tr').attr("data-id");   
     var nameCol = $(this).find('[data-attribute="so_name"]');
         nameCol.append('&nbsp;&nbsp;<input type="button" value="C" onclick="CreateOffice();" style="color:orange; border-style: none;" />');
	 nameCol.append('&nbsp;&nbsp;<input type="button" value="R" onclick="RetrieveOffice(\''+ recId +'\');" style="color:orange; border-style: none;" />');
	 nameCol.append('&nbsp;&nbsp;<input type="button" value="U" onclick="UpdateOffice(\''+ recId +'\');" style="color:orange; border-style: none;" />');
	 nameCol.append('&nbsp;&nbsp;<input type="button" value="D" onclick="DeleteOffice(\''+ recId +'\');" style="color:orange; border-style: none;" />');
   }

});
});
}); 

Above steps resulted below. You may notice four buttons to trigger the operations.


Now we have below four functions those do the respective operations.

function CreateOffice(RecId)
{
    alert("Creating Office..");
    webapi.safeAjax({
        type: "POST",
        url: "/_api/so_offices",
        contentType: "application/json",
        data: JSON.stringify({
        "so_name": "Created Office"
        }),
        success: function (res, status, xhr) {
        console.log("entityID: "+ xhr.getResponseHeader("entityid"));
        document.location.reload(true);
        }
    });
}

function RetrieveOffice(RecId)
{
    alert("Retrieving.." + RecId);
    webapi.safeAjax({
	   type: "GET",
	   //url: "/_api/so_offices?$select=so_type,so_revenue",
           url: "/_api/so_offices("+ RecId +")?$select=so_type,so_revenue", 
		contentType: "application/json",
		success: function (res) {
			console.log(res);
            //alert(res.value[0].so_revenue);
            alert("Type :" + res.so_revenue);
		}
	});
}

function UpdateOffice(RecId)
{
    alert("Updating Office..XX" + RecId);
    webapi.safeAjax({
        type: "PATCH",
        url: "/_api/so_offices("+ RecId +")",
        contentType: "application/json",
        data: JSON.stringify({
        "so_name": "Office Updated"
        }),
        success: function (res) {
        console.log(res);
        document.location.reload(true);
        }
    });
}

function DeleteOffice(RecId)
{
    alert("Deleting Office.." + RecId);
    webapi.safeAjax({
        type: "DELETE",
        url: "/_api/so_offices("+ RecId +")",
        contentType: "application/json",
        success: function (res) {
        console.log(res);
        document.location.reload(true);
        }
    });
}

None of the above will work unless we add below Wrapper AJAX function. So added this as well.

(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)

Hope this code snippet helps!