Thursday 28 January 2016

ModifyAccess Message Plugin CRM C#

Introduction

In my previous post, explained about Share and Unshare Access which will trigger GrantAccess and also RevokeAccess. But those message, as explained before will only be triggered if you Share the record to User or Team that has no previous added access at all or also Unshare the record completely removing the access from the previous User or Team.

How about if you want to have your own logic when you change the Access only to the specific users that already have the access, either you remove (not completely) and also add from the existing access so long you Update or Modify it, for End users, they just know about Share, but CRM has its own message request triggered.

The Code

And here is the code..

*You can continue from my previous post code

else if (context.MessageName == "ModifyAccess")
            {
                // Obtain the target entity from the input parameter.
                EntityReference EntityRef = (EntityReference)context.InputParameters["Target"];
                //after get this EntityRef then will be easy to continue the logic

                //Obtain the principal access object from the input parameter
                Microsoft.Crm.Sdk.Messages.PrincipalAccess PrincipalAccess = (Microsoft.Crm.Sdk.Messages.PrincipalAccess)context.InputParameters["PrincipalAccess"];

                //Then got the User or Team and also Access Control that being Granted

                //***to Get User/Team that being Shared With
                var userOrTeam = PrincipalAccess.Principal;
                var userOrTeamId = userOrTeam.Id;
                var userOrTeamName = userOrTeam.Name; //this entityReference will not return name, only ID
                var userOrTeamLogicalName = userOrTeam.LogicalName;

                //use the logical Name to know whether this is User or Team!
                if (userOrTeamLogicalName == "team")
                {
                    //what you are going to do if shared to Team?
                }
                if (userOrTeamLogicalName == "systemuser")
                {
                    //what you are going to do if shared to Team?
                }

                Trace(userOrTeamId.ToString());
                Trace(userOrTeamLogicalName);

                //***to Get the Principal Access
                var AccessMask = PrincipalAccess.AccessMask;
                Trace(AccessMask.ToString());

                throw new InvalidPluginExecutionException("ModifyAccess triggered"); //please remove later

                //your logic continue here after already have all of them
            }

And here is the complete code

void Post_Message_GrantAccess(IServiceProvider serviceProvider, IPluginExecutionContext context)
        {
            if (context.MessageName == "GrantAccess")
            {
                //this GrantAccess is for sharing
                //for Unshare, messagename = "RevokeAccess" and will do the same passing parameters

                // Obtain the target entity from the input parameter.
                EntityReference EntityRef = (EntityReference)context.InputParameters["Target"];
                //after get this EntityRef then will be easy to continue the logic

                //Obtain the principal access object from the input parameter
                Microsoft.Crm.Sdk.Messages.PrincipalAccess PrincipalAccess = (Microsoft.Crm.Sdk.Messages.PrincipalAccess)context.InputParameters["PrincipalAccess"];

                //Then got the User or Team and also Access Control that being Granted

                //***to Get User/Team that being Shared With
                var userOrTeam = PrincipalAccess.Principal;
                var userOrTeamId = userOrTeam.Id;
                var userOrTeamName = userOrTeam.Name; //entityReference only return ID, not Name so will be empty string here
                var userOrTeamLogicalName = userOrTeam.LogicalName;

                //use the logical Name to know whether this is User or Team!
                if (userOrTeamLogicalName == "team")
                {
                    //what you are going to do if shared to Team?
                }
                if (userOrTeamLogicalName == "systemuser")
                {
                    //what you are going to do if shared to Team?
                }

                Trace(userOrTeamId.ToString());
                Trace(userOrTeamLogicalName);

                //***to Get the Principal Access
                var AccessMask = PrincipalAccess.AccessMask;
                Trace(AccessMask.ToString());

                //throw new InvalidPluginExecutionException("Shared"); //please remove later

                //your logic continue here after already have all of them

            }
                
            else if (context.MessageName == "RevokeAccess")
            {
                // Obtain the target entity from the input parameter.
                EntityReference EntityRef = (EntityReference)context.InputParameters["Target"];
                //after get this EntityRef then will be easy to continue the logic

                //Unshare does not have PrincipalAccess because it removes all, only can get the revokee
                //Obtain the principal access object from the input parameter
                var Revokee = (EntityReference)context.InputParameters["Revokee"];
                var RevokeeId = Revokee.Id;
                var RevokeeLogicalName = Revokee.LogicalName; //this one Team or User

                Trace(RevokeeId.ToString());
                Trace(RevokeeLogicalName);

                throw new InvalidPluginExecutionException("Unshared"); //please remove later
            }

            else if (context.MessageName == "ModifyAccess")
            {
                // Obtain the target entity from the input parameter.
                EntityReference EntityRef = (EntityReference)context.InputParameters["Target"];
                //after get this EntityRef then will be easy to continue the logic

                //Obtain the principal access object from the input parameter
                Microsoft.Crm.Sdk.Messages.PrincipalAccess PrincipalAccess = (Microsoft.Crm.Sdk.Messages.PrincipalAccess)context.InputParameters["PrincipalAccess"];

                //Then got the User or Team and also Access Control that being Granted

                //***to Get User/Team that being Shared With
                var userOrTeam = PrincipalAccess.Principal;
                var userOrTeamId = userOrTeam.Id;
                var userOrTeamName = userOrTeam.Name;
                var userOrTeamLogicalName = userOrTeam.LogicalName;

                //use the logical Name to know whether this is User or Team!
                if (userOrTeamLogicalName == "team")
                {
                    //what you are going to do if shared to Team?
                }
                if (userOrTeamLogicalName == "systemuser")
                {
                    //what you are going to do if shared to Team?
                }

                Trace(userOrTeamId.ToString());
                Trace(userOrTeamLogicalName);

                //***to Get the Principal Access
                var AccessMask = PrincipalAccess.AccessMask;
                Trace(AccessMask.ToString());

                throw new InvalidPluginExecutionException("ModifyAccess triggered"); //please remove later

                //your logic continue here after already have all of them
            }
        }

Register the Plugin

And how to register the Plugin?
Easy…

Just add more event so-called ModifyAccess



So for the complete solution, you will have 3 steps registered in the Plugin
1. GrantAccess
2. RevokeAccess
3. ModifyAccess

Result

And here is the result..
I just want to modify the Access of this User from Read & Write to Read & Append & Share



Then here is the Trace result



*Remember to always remove the “throw new InvalidPluginExecutionException” because it will stop your process.

And I hope this can help you!
This is the requirement in my project that currently I am doing it so basically is helping me to document it!

Monday 25 January 2016

Plugin Triggered When Share/Unshare CRM C#

Introduction

Hi everyone, I just want to share how Share/Unshare in CRM will trigger a plugin or just in case you need to do some logic after users has done the Share/Unshare records in CRM.

Especially if you want to Share the child records once you share the parent record, yes you can do the Cascading behaviour, but you might be aware that this parental and configurable cascading behaviour can only applied to 1 relationship, so imagine you have many entities, which this problem is the one i am facing right now.

The Problem

In the Plugin Registration Tool, there is no Share or Un-Share Messages and you see MSDN also does not have!



So meaning CRM does not allow the injection of the Share or Un-Share logic?

No..CRM does allow, just the Message Name is..

GrantAccess and RevokeAccess

Meanwhile you also can get the Parameters from that Request

The Code

This is the Code!

        //pass the context here from your common Execute function
        void Post_Message_GrantOrRevokeAccess(IServiceProvider serviceProvider, IPluginExecutionContext context)
        {
            if (context.MessageName == "GrantAccess")
            {
                //this GrantAccess is for sharing
                //for Unshare, messagename = "RevokeAccess" and will do the same passing parameters

                // Obtain the target entity from the input parameter.
                EntityReference EntityRef = (EntityReference)context.InputParameters["Target"];
                //after get this EntityRef then will be easy to continue the logic

                //Obtain the principal access object from the input parameter
                Microsoft.Crm.Sdk.Messages.PrincipalAccess PrincipalAccess = (Microsoft.Crm.Sdk.Messages.PrincipalAccess)context.InputParameters["PrincipalAccess"];

                //Then got the User or Team and also Access Control that being Granted

                //***to Get User/Team that being Shared With
                var userOrTeam = PrincipalAccess.Principal;
                var userOrTeamId = userOrTeam.Id;
                var userOrTeamName = userOrTeam.Name;
//this userOrTeam.Name will be blank since entityReference only will give you ID
                var userOrTeamLogicalName = userOrTeam.LogicalName;

                //use the logical Name to know whether this is User or Team!
                if (userOrTeamLogicalName == "team")
                {
                    //what you are going to do if shared to Team?
                }
                if (userOrTeamLogicalName == "systemuser")
                {
                    //what you are going to do if shared to Team?
                }

                Trace(userOrTeamId.ToString());
                Trace(userOrTeamLogicalName);

                //***to Get the Principal Access
                var AccessMask = PrincipalAccess.AccessMask;
                Trace(AccessMask.ToString());

                throw new InvalidPluginExecutionException("to trigger the trace only"); //please remove later

                //your logic continue here after already have all of them

            }
            else if (context.MessageName == "RevokeAccess")
            {
                 // Obtain the target entity from the input parameter.
                EntityReference EntityRef = (EntityReference)context.InputParameters["Target"];
                //after get this EntityRef then will be easy to continue the logic

                //Unshare does not have PrincipalAccess because it removes all, only can get the revokee
                //Obtain the principal access object from the input parameter
                var Revokee = (EntityReference)context.InputParameters["Revokee"];
                var RevokeeId = Revokee.Id;
                var RevokeeLogicalName = Revokee.LogicalName; //this one Team or User

                Trace(RevokeeId.ToString());
                Trace(RevokeeLogicalName);

                throw new InvalidPluginExecutionException("Unshared"); //please remove later
            }
        }

I just give you the concept that you can just continue from it..

Register the Plugin

Yup this is the last step, just using your favorite Plugin Registration Tool will do.



Result from the Trace

And here is the result that you can see, you can get the user or Team you have shared With and also What is the Access Mask

I try to share to a CRM User: Read and Write




So you can get the full Parameter in your plugin

Here is the Trace result



*As you can see you can get the System User ID as well Team ID if you share to Team and also the privilege, ReadAccess and also WriteAccess

How about UnShare?

Same as well!

I completely remove the CRMUser from all his previous shared access (Read & Write)



And this is the Trace result




*But, you need to Remember that Share here meaning performing Share to the user or Team that previously did not have any Access, it will trigger the GRANTACCESS event.
And also Unshare means Completely remove the Access, then it will trigger the REVOKEACCESS event.

If you want to only modify the Access (ex: from Read and Write to Read only), then you need to register your plugin to another message, so call MODIFYACCESS which i will tell you about this in the my next post..(I hope soon).

*Remember to remove the Trace, I use my own function to do trace in order let you know the Result and also you might not need it

And I hope this is helpful for you guys!
Thank you.

Wednesday 20 January 2016

An item with the same key has already been added Error when try to Import CRM 2011/2013/2015/2016 Unmanaged Solution

Hi All,

I just try to document what just happened to remind myself and help other people if needed.

So basically when try to import solution from our Development Organization to another CRM Organization, we encountered the problem as described in the system.

Then here how to solve:

1. Always download the Log
*Always try to download the Log will let you know better

2. Analyze the Log, which process failed and got error

3. In my log, the Component type you can see in which entity and also with Component type, I found it was "Form"

So I try to minimalize the effort by creating new solution with the component only using the troubled entity, easier to do checking in the end because only one component included.

Then I searched the Google and found useful information.

http://nishantrana.me/2014/04/15/an-item-with-the-same-key-has-already-been-added-error-while-importing-solution-in-crm-2013/

https://social.microsoft.com/Forums/en-US/eac3b06b-5cec-43f4-979e-9e40ed113042/crm-2013-import-solution-an-item-with-the-same-key-has-already-been-added-error-while-processing?forum=crmdevelopment

*Thanks my friend Nishantrana

yeah, it is definitely because of there is at least one field with same schema name but different data type.

But in my case is not same.

In the end I also realize that I have same schema name, same data type, but it is Option Set.
So, someone deleted the field and re-create new one with same schema name, but one is Local Option Set and another one is Global Option Set,

That was causing the problem.

So how I solve in the end?

I delete the field, remove from form as well in the destination then after that I publish All Customizations and then I re-import again the Solution.

Conclusion:

My Friends, this case is really common issue and try to reduce the possibilities by downloading the log and also using smaller solution with the specific component to check.

Not only if the data type is different, but if it is an Option Set, local and global Option you also need to care.

Try to avoid this by always check your schema name or if not commit do not create the field first (if possible only).

Hope this helps!
Thanks.

Wednesday 13 January 2016

CRM 2013/2015 Sitemap Icons Are Not Displayed Correctly Workaround

Hi Friends,

Brief Introduction

if you encounter a problem that you cannot see the icon in your customized Sitemap, do not worry, it is known issue, but there will be a workaround.

Or you just take a look on these submitted Forum posts as known CRM Issues that has been submitted to MS Connect as well

The Forum

https://community.dynamics.com/crm/f/117/t/119230#responses
https://community.dynamics.com/crm/f/117/t/152121
https://community.dynamics.com/crm/f/117/t/160354

The Issue

Here is the example, of the incorrect ICON






The Workaround

Basically, it was caused by the URL of the Image that is not correct rendered in the CRM Web, it keeps rendering the root location, not the real path.



It is referring to the WebResources/imagename.png not the customized folder path, so it is pointing to the root folder, instead.

Knowing & using this Concept, so tried to have workaround by uploading new image for the icon using the expected path.

And this is needing our best buddy tool, no other than XrmToolBox, thanks Tanguy to ease me doing this.




Or if you use Web Resource common way


The Result:

After the changing of the URL





Hope this helps
Thanks

Aggregate Fetch XML Calculation C# CRM

Hi Just want to share the Code to Calculate the Aggregate in CRM, especially for those who are in the CRM version, older than 2015 so that there will be no calculated and roll-up field so far.
Using this helper code you just need to pass the Parent Id, and it will do the rest of Calculation and of course you just need to supply the Operator (like SUM, Count, AVG, etc)

public string CalculateAggregateFetchXML(string strEntityName, string strAggregateAttributeName,
                                                       AggregateOperator aggregateOperator, string strFilterXML) {
            string strFetchXML = string.Empty;
            string strAggregateAlias = "nec_alias";

            strFetchXML = string.Format(@" 
                            <fetch distinct='false' mapping='logical' aggregate='true'> 
                                <entity name='{0}'> 
                                    <attribute name='{1}' alias='{2}' aggregate='{3}' />
                                    {4}
                                </entity> 
                            </fetch>", strEntityName, strAggregateAttributeName, strAggregateAlias, aggregateOperator.ToString(), strFilterXML);

            EntityCollection aggregateResult = CrmService.RetrieveMultiple(new FetchExpression(strFetchXML));
            decimal totalValue = 0;

            foreach (var c in aggregateResult.Entities) {
                decimal aggregate2 = 0;
                if (c.Attributes.Contains(strAggregateAlias)) {
                    AliasedValue alias = ((AliasedValue)c[strAggregateAlias]);

                    if (alias.Value is Money) {
                        aggregate2 = ((Money)((AliasedValue)c[strAggregateAlias]).Value).Value;
                    } else if (alias.Value is Int32 || alias.Value is int) {
                        aggregate2 = ((int)((AliasedValue)c[strAggregateAlias]).Value);
                    } else if (alias.Value is Decimal || alias.Value is decimal) {
                        aggregate2 = ((decimal)((AliasedValue)c[strAggregateAlias]).Value);
                    }

                    totalValue = aggregate2;
                }
            }
            return totalValue.ToString();
        }

And the AggregateOperator Enum

public enum AggregateOperator {
            [Description("sum")]
            sum = 1,
            //
            [Description("avg")]
            avg = 2,
            //
            [Description("min")]
            min = 3,
            //
            [Description("max")]
            max = 4,
            //
            [Description("count(*)")]
            count = 5,
            //
            [Description("countcolumn")]
            countcolumn = 5,
        }

This usage is very easy

strTotalAttByRace = CrmContext.CalculateAggregateFetchXML(“entityname” “primaryidfieldname”,
                                                  NEC.ESBU.Helpers.CrmHelper.AggregateOperator.count,strFilterXML);

//strFilterXML You can skip this or use this in case you need more condition

It will result you a string or you can change to Integer, that is the total aggregate result

*Example for Aggregate Result with fetch xml

Is to return total Attendance by Race (that stored in the Contact entity)

Attendance is many to 1 relationship with Contact

strFilterXML = string.Format(@"<filter type='and'> 
                                                <condition attribute='{0}' operator='eq' uitype='{1}' value='{2}' />
                                                <condition attribute='pa_sessionid' operator='eq' uitype='pa_session' value='{3}' />
                                                <condition attribute='pa_type' operator='eq' value='{4}' />
                                            </filter>
                                            <link-entity name='contact' from='contactid' to='pa_participantid' alias='af'>
                                                <filter type='and'>
                                                    <condition attribute='pa_race' operator='eq' value='{5}' />
                                                </filter>
                                            </link-entity>",
                            strAttributeProductIdName, strEntityProductIdName, guidProductId.ToString(), guidSessionId.ToString(),
                            (int)productType, (int)raceType);

This will be very useful if you want to calculate child record, either SUM, COUNT, or AVG for instance, then just pass the parent ID or add more fetch XML to add more filter

*Work for Money, Whole Number, and Decimal data type

Hope this helps!
Thanks