Auto deploy on save from Visual Studio for Adxstudio Portal artefacts in new version of CRMQuickDeploy

I have released a new version of CRMQuickDeploy with the following improvements:

  • Option to auto deploy on save for Portal artefacts
  • Ability to specify MIME Type and Display Order for Portal web files

Auto deploy on save for Portal artefacts

You can now enable this option to have Visual Studio automatically deploys a Portal artefact file when you save it from the code editor.

This option is disabled by default, but you can enable it by going to Tools \ Options \ CRMQuickDeploy.

Note that this option is only applicable to Portal artefacts.

Ability to specify MIME Type and Display Order for Portal web files

Configuration settings for web files are defined in the DeploymentSettings.xml file. This file now allows you to specify MIME Type and Display Order for web files. Below is an annotated example XML.

<?xml version="1.0" encoding="utf-8" ?>
<DeploymentSettings>

	<!-- Define a collection of items that will be deployed as 
Web Files and associated with a particular website. -->
	<WebFiles>

		<!-- Separately define each local files to be 
deployed as Web Files.

localPath: Required. Relative to the root PortalWebFiles 
folder of the project. Value should not start with '\'. 

targetName: Optional. The name of the web file once deployed.
Default to the same value as partialUrl. 

parentPage: Required. The name of the parent web page in CRM.

partialUrl: Optional. The partial URL of the web file once 
deployed. Default to the file name of the Visual Studio item 
with all spaces removed. 

mimeType: Optional. The MIME type of the file. If not 
specified, the MIME type will be determined based on the 
file extension. 

displayOrder: Optional. The display order of the file. 
Must be an integer. 

The file extension to MIME type mapping is as follow: 

txt: text/plain 
css: txt/css 
js: txt/javascript 
htm, html: text/html 
gif: image/gif 
jpg, jpeg: image/jpeg 
png: image/png 
svg: image/svg+xml 
ico: image/x-icon 
xml: application/xml 
json: application/json 
all other extensions: application/octet-stream -->
		<File localPath="common.js" parentPage="Home" />
		<File localPath="scripts\script1.js" parentPage="Home" />
		<File localPath="styles\styles.css" targetName="Portal Styles" parentPage="Home" partialUrl="styles.css" mimeType="text/css" displayOrder="1"/>

		<!-- Define local files to be deployed as Web files 
using the folder approach. When this approach is used, default 
value will be used for targetName and partialUrl for each item 
under the folder. mimeType for each item will be determined based 
on the file extension, unless the item is specified in the 
folder's MimeTypeOverride child element. A Folder element only 
covers the files that are found directly underneath that folder. 
Separate Folder elements must be defined for each sub-folders. 

localPath: Required. Relative to the root PortalWebFiles folder 
of the project. Value should not start with '\'. To refer to 
files directly underneath the root PortalWebFiles, specify an 
empty string for this attribute, i.e. localPath="". 

parentPage: Required. The name of the parent web page in CRM 
that will be applied to all items under this folder. 

targetNamePrefix: Optional. The specified value will be added 
to the targetName for all items under this folder. -->
		<Folder localPath="scripts" parentPage="Home" />
		<Folder localPath="styles" parentPage="Home" targetNamePrefix="My Portal - ">
			
			<!-- Define MIME type for selected items underneath 
this folder. This element is optional. -->
			<MimeTypeOverride>
			
				<!-- Define MIME type for items underneath 
this folder. The MIME type for an item will be automatically 
determined based on the file extension if the item is not 
defined here. 

name: Required. The filename of the item. Value should not start 
with '\'. 

mimeType: Required. The MIME type for the item. -->
				<File name="styles.css" mimeType="text/css" />
			</MimeTypeOverride>
			
			<!-- Define display order for selected items 
underneath the folder. This element is optional. -->
			<DisplayOrderOverride>
			
				<!-- Define display order for items underneath 
this folder. The display order for an item will be blank if the 
item is not defined here. 

name: Required. The filename of the item. Value should not 
start with '\'. 

displayOrder: Required. Must be an integer. -->
				<File name="styles.css" displayOrder="1" />
			</DisplayOrderOverride>
		</Folder>
	</WebFiles>
</DeploymentSettings>

Download

You can download this extension from the Visual Studio Marketplace.

I hope you find this addition useful!

Advertisements
Posted in Adxstudio, CRM, CRM Portal, CRMQuickDeploy | Leave a comment

Enhancing Quick Find in CRM with configurable custom query language

Unlike Advanced Find, Quick Find in CRM is pretty rudimentary. While you can specify the search columns in the Quick Find View, the Quick Find text query does not allow you to specify field filtering on the target record, or on one or more levels of related records.

Advanced Find is great for performing these types of more complex searches, but it requires multiple clicks and is time consuming to setup. This is not ideal in scenarios where users need to be able to find records quickly and there are a high number of records and enquiries, e.g. in call centres.

Here are some examples of searches that are not possible with Quick Find:

  • Find Accounts where “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields)
  • Find Accounts where Account Name contains “tech”, and “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields), and that Contact has a Case that contains “laptop problem” in Case Title or Description
  • Find Accounts where any associated Contact has a Case regarding the Product “Laptop”
  • Find Accounts where any associated Contact has a Case regarding the Product “Laptop”, and where the Account has an associated Opportunity that contains “laptop” in the Description

I have developed a solution that allows users to perform searches similar to the above using Quick Find. The solution essentially augment Quick Find with a custom query language that allows users to specify filtering in the query text. With exception for syntax, the query language is configurable and can be extended to OOTB and custom entities. In fact, the query syntax is similar to that used by Outlook, Gmail and SharePoint.

For example, the enhanced query allows users to perform the above searches using the following:

Search Enhanced Query Examples (Configurable)
Find Accounts where “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields)  contact:mccloud
Find Accounts where Account Name contains “tech”, and “McCloud” is a Contact (in First Name, Last Name or Personal Notes fields), and that Contact has a Case that contains “laptop problem” in Case Title or Description  *tech contact:mccloud\case:”laptop problem”
Find Accounts where any associated Contact has a Case regarding the Product “Laptop”  contact\case\product:laptop

or shortcut:

case-prd:laptop

Find Accounts where any associated Contact has a Case regarding the Product “Laptop”, and where the Account has an associated Opportunity that contains “laptop” in the Description case-prd:laptop opp:laptop

So how does it work?

After installing the solution (download at the end of this post), the Quick Find Configurations entity is available to system administrators under Settings.

Create a Quick Find Configuration record for each entity where you want to enable the enhanced Quick Find.

The following fields are required:

Field Description
Entity Name The CRM name of the target entity. Type-ahead is enabled to help you locate the correct entity.
Configuration Name The name of the Quick Find Configuration record. For descriptive purpose only. Defaults to the display name of the selected target entity.
Configuration XML that describes the search schema for the target entity. A skeleton XML is automatically generated for you by default but you will need to update it. The schema of this XML is described in details later in this post.

Enhanced Quick Find is automatically enabled for the target entity once you save the Quick Find Configuration record.

Once enabled, users can start using the enhanced query in the target entity’s Quick Find:

You can update the enhanced Quick Find configuration for any entity at anytime by editing the relevant Quick Find Configuration record. To disable enhanced Quick Find for an entity, delete or deactivate the relevant configuration record. Reactivating a Quick Find Configuration record re-enables enhanced Quick Find for the relevant entity.

Hmm… what is the query syntax?

The syntax for the enhanced query is as follow:

The query can contain text criteria and filter parts.

  • Text criteria:   – this is searched against the Quick Find columns of the target entity as per OOTB behaviour
  • Filter parts:     – these apply additional filters to the result set that would be returned by the text criteria

Text criteria and filter parts are optional and can be specified in any order. A query can contain multiple filter parts. The AND operator is used between the text criteria and specified filter parts.

Filter part

A filter part consists of an alias for a related entity, and a criteria for that entity. The alias, and the fields to search against for that alias, are configurable in the configuration schema (more on this later). A colon (:) is used to separate the alias and its criteria. Wrap the criteria in double quotes (“) if it is more than one word. Criteria are searched against defined search fields using the LIKE operator.

A filter part can target a related entity at any level of the relationship hierarchy (grandchild, great grandchild etc.). This is done by using the backslash character (\) to specify the relationship path.

Here are some examples.

Example Effect
contact:abc Filter the result set to those with a related ‘contact’ where ‘contact’ matches ‘abc’
contact\case:xyz Filter the result set to those with a related ‘contact’ where ‘contact’ has a related ‘case’, AND ‘case’ matches ‘xyz’
contact:”abc 12″\case:xyz Filter the result set to those with a related ‘contact’ where ‘contact’ matches ‘abc 12’, AND contact has a related ‘case’ that matches ‘xyz’

Ok… tell me about this configuration schema!

The XML that you specify in the Configuration field of the configuration record defines the search schema and query language available to the users for the target entity.

Below is the annotated XML configuration that would enable the example searches earlier on in this post.

<?xml version="1.0" encoding="utf-8" ?>

<!-- enableLogging: Optional. When set to true, details of how 
the query was interpreted are logged to CRM's tracing service.
Default is false. -->
<QuickFindConfiguration enableLogging="false">
	<QueryTransformation>
		<AliasReplacements>

			<!-- Defines a set of alias replacements for the 
query. Aliases matching the list below will be replaced with the 
specified values before the query is processed. Multiple alias 
replacements are supported. 

	replace: Required. The alias to look for. 
	with: Required. The value to replace the match with. -->
			<AliasReplacement replace="case-prd" with="contact\case\product"/>
		</AliasReplacements>
	</QueryTransformation>
	
	<!-- Defines an entity for quick find augmentation. The 
target entity of the quick find MUST be defined, as well as 
other relationships that are allowed in the augmented query. 

	entityName: Required. The CRM name of the entity. -->
	<Entity entityName="account">
		<Relationships>

			<!-- Defines a relationship from/to the entity 
that can be searched in the query. Multiple relationships are 
supported. 

	alias: Required. The alias used in the query to refer to the 
related entity, e.g. contact:ABC. 

	entityName: Required. The CRM name of the related entity. 
An Entity element with a matching entityName MUST also be 
defined. 

	fromField: Required. The name of the field that establishes 
the relationship between the parent entity and the related 
entity. 

	toField: Required. The name of the field that establishes 
the relationship between the parent entity and the related 
entity. -->
			<Relationship alias="contact" entityName="contact" fromField="parentcustomerid" toField="accountid"/>
			<Relationship alias="opp" entityName="opportunity" fromField="parentaccountid" toField="accountid"/>
		</Relationships>
		<FieldGroups>

			<!-- Defines a virtual grouping of fields 
on the target entity of the quick find. The grouping can be 
referred to in the query using an alias, e.g. profile:"XYZ". 
Groupings will only be applied against the target entity of the 
quick find. Multiple groupings are supported. 

	alias: Required. The alias used in the query to refer to 
this group of fields on the target entity. -->
			<FieldGroup alias="profile">
				<SearchFields>

					<!-- Defines a field on 
the target entity that belongs to this grouping. Multiple 
fields are supported and are applied to the search using the OR 
operator. 

	name: Required. The CRM name of the field. -->
					<Field name="industrycodename"/>
					<Field name="sic"/>
				</SearchFields>
			</FieldGroup>
		</FieldGroups>
	</Entity>
	<Entity entityName="contact">
		<SearchFields>

			<!-- Defines a field on this particular 
entity that will be used in the query for filtering. Multiple 
fields are supported and are applied to the search using the 
OR operator. 

	name: Required. The CRM name of the field. -->
			<Field name="firstname"/>
			<Field name="lastname"/>
			<Field name="description"/>
		</SearchFields>
		<Relationships>
			<Relationship alias="case" entityName="incident" fromField="customerid" toField="contactid"/>
		</Relationships>
	</Entity>
	<Entity entityName="incident">
		<SearchFields>
			<Field name="title"/>
			<Field name="description"/>
		</SearchFields>
		<Relationships>
			<Relationship alias="product" entityName="product" fromField="productid" toField="productid"/>
		</Relationships>
	</Entity>
	<Entity entityName="product">
		<SearchFields>
			<Field name="name"/>
		</SearchFields>
	</Entity>
	<Entity entityName="opportunity">
		<SearchFields>
			<Field name="description"/>
		</SearchFields>
	</Entity>
</QuickFindConfiguration>

Entity element

Entity elements define the entities that may be involved in the enhanced Quick Find for the target entity. An Entity element must exist for the target entity. An Entity element must also exist for any relationship that the user should be able to filter on in the enhanced query.

Relationship element

Relationship is a child element of an Entity element, and defines the related entities that could be used in the query for that parent entity.

The Relationship element defines the alias that the user can use to refer to the related entity in the query. A corresponding Entity element must exist for the related entity identified by the Relationship element.

SearchFields/Field element

The SearchFields/Field element is a child element of an Entity element, and is used to define a field that should be searched against when filtering on that entity. Multiple SearchFields/Field elements can be specified, in which case the OR operator will be used when applying the filter criteria against that particular entity.

FieldGroups/FieldGroup element

The FieldGroups/FieldGroup element is a child element of the Entity element, and allows you to define an alias that users can use to apply filtering criteria directly to the target entity (rather than related entities).

Normally the filter ‘alias:criteria‘ is applied to a related record type (identified by alias). The FieldGroup element allows to you define an alias that refers to a group of fields on the target entity.

For example, Account has an Industry and SIC Code fields. Using the FieldGroup element, you can enable users to filter against these particular fields when searching for Accounts.

Given the XML above, users can search for Accounts with ‘software’ in Account Name and ‘tech’ in the Industry or SIC Code fields using:

*software profile:tech

A FieldGroup element contains one or more SearchFields/Field elements that define the fields belonging to this group. When multiple fields are specified, the OR operator will be used when applying the filter criteria.

Note that FieldGroup elements are only applicable to the target entity.

QueryTransformation/AliasReplacements/AliasReplacement element

This element allows you to apply transformation to the user’s query before it is processed. This allows you to define shortcuts that users can use in their query. This is particularly useful when filtering against many-to-many related records, as without shortcuts, users would need to filter from the target entity to the many-to-many joining table, and then to the intended related record.

Filtering against OptionSet fields

Criteria are applied using the LIKE operator. When including an OptionSet field in the configuration schema you therefore should use the corresponding name field of the OptionSet field.

In case you are not already aware, all OptionSet fields in CRM has a corresponding ‘name’ field, which stores the text value of the selected OptionSet value. For example, Account has an Industry pick-list field. The name of this field is ‘industrycode‘ and the name of the corresponding OptionSet name field is ‘industrycodename‘.

Filtering against many-t0-many relationships

Each many-to-many relationship in CRM consists of two relationships under the hood: a 1-to-many relationship from entity A to the joining table, and a many-to-1 relationship from the joining table to entity B.

In order to filter against a many-to-many relationship in the enhanced query, you would need to specify both of these underlying relationships in the configuration schema. It is also recommended that you add a query transformation to give users a shortcut to filter the related entity more naturally.

For example, the configuration XML below can be used to enable filtering of Lead by Competitor.

<?xml version="1.0" encoding="utf-8" ?>
<QuickFindConfiguration enableLogging="false">
	<QueryTransformation>
		<AliasReplacements>
			<AliasReplacement replace="comp" with="leadcomps\comp"/>
		</AliasReplacements>
	</QueryTransformation>
	
	<Entity entityName="lead">
		<Relationships>
			<Relationship alias="leadcomps" entityName="leadcompetitors" fromField="leadid" toField="leadid"/>
		</Relationships>
	</Entity>
	<Entity entityName="leadcompetitors">
		<Relationships>
			<Relationship alias="comp" entityName="competitor" fromField="competitorid" toField="competitorid"/>
		</Relationships>
	</Entity>
	<Entity entityName="competitor">
		<SearchFields>
			<Field name="name"/>
		</SearchFields>
	</Entity>
</QuickFindConfiguration>

The query would be:

leadcomps\comp:”Competitor A”

or more naturally (thanks to QueryTransformation):

comp:”Competitor A”

How does it work underneath the hood?

When a Quick Find Configuration record is created, a plugin is triggered. This plugin automatically registers a new plugin on RetrieveMultiple of the target entity. The XML configuration is passed to the RetrieveMultiple plugin via its unsecured configuration.

The RetrieveMultiple plugin intercepts users’ queries and transforms it according to the XML configuration.

How about performance impact?

Performance impact of this solution is virtually none to minimal. This is because the majority of the work is in-memory string parsing and manipulation of the query object. There are no additional service calls made to CRM.

Where can users use this?

The enhanced query will be recognised in the following places:

  • Quick Find on the grid of enabled entities
  • Searching in look up forms

Unfortunately it won’t be recognised in the multi-entity search.

Support for OnPrem and Online

This solution works on both OnPrem and Online.

Download

You can download the solution ZIP here:

Note that only the managed solution adds the Quick Find Configuration entity to the sitemap. For the unmanaged solution, you will need to do this manually, or use Advanced Find to retrieve and create new records of this entity.

So in conclusion…

This solution enhances the OOTB Quick Find with a configurable query language and search schema that allow users to use text query to perform advanced searches. Users with exposure to search query in SharePoint, Outlook or Gmail should find this enhanced query syntax familiar.

Posted in CRM | Leave a comment

Source-control Adxstudio/CRM Portal JS, CSS and Liquid with CRMQuickDeploy

When working with Adxstudio Portal (OnPrem) or CRM Portal (Online) you will find that JavaScript, CSS and Liquid web templates are stored in CRM records (i.e. CRM database), which is not good for source-control or scenarios where there are more than one developer actively working on the system.

CRMQuickDeploy now has the ability to deploy these artefacts from Visual Studio to Adxstudio/CRM Portal, including:

  • Web Files
  • Web Templates
  • Entity Lists (JavaScript only)
  • Entity Forms (JavaScript only)
  • Web Form Steps (JavaScript only)
  • Web Pages (JavaScript, CSS and Copy (HTML) only)

Note that currently the tool does not deploy the configuration of these artefacts, but only the associated JavaScript, CSS and Liquid content as indicated above.

This allows you to edit JavaScript, CSS and HTML using the full editing power of Visual Studio with tight source-control integration. Having source-control, and using this as the source of truth for your source code, also allows multiple developers to actively work on the system concurrently.

Download

You can download CRMQuickDeploy from the Visual Studio Marketplace.

Support for OnPrem and Online

This tool supports both Adxstudio OnPrem (at the time of writing currently at version 7.0.0.25) and CRM Portal (Online). There are some minor differences in behaviour however depending on the type of artefact being deployed. These are highlighted in the relevant sections below.

Overview of how it works

The tool requires portal artefacts in the Visual Studio project to be placed into specific folders based on the artefact type. For example, web files should be placed in the PortalWebFiles folder, and web templates in the PortalWebTemplates folder, etc.

In most cases, the linkage between a file in Visual Studio and the corresponding record in CRM is by the name of the file in Visual Studio. For example, the file PortalWebPages\Contact Us.css will be deployed as the CSS for the Contact Us web page in CRM.

You can deploy an item by:

  1. Right-click in the active code editor and choose Deploy to CRM
  2. Right-click on one or more files (or a folder) in the Solution Explorer and choose Deploy to CRM
  3. Right click on the project in Solution Explorer and choose Deploy Portal Artefacts. This will deploy all portal artefacts found within the project.

The deployment behaviour for each artefact type are outlined later in this post, but here’s a summary table:

Artefact Type Expected Folder Name Create or Update? Linkage to CRM Record
Web page PortalWebPages Update only Via the filename in Visual Studio
Web template PortalWebTemplates Create and Update Via the filename in Visual Studio
Entity list PortalEntityLists Update only Via the filename in Visual Studio
Entity form PortalEntityForms Update only Via the filename in Visual Studio
Web form step PortalWebForms Update only Via the filename in Visual Studio
Web file PortalWebFiles Create and Update Via a special XML config file in Visual Studio

Specifying CRM connection string and target portal website

Similar to previous versions of the tool, the CRM connection string is specified at the solution node in the Solution Explorer:

Some artefact types need to be scoped to a particular website record in CRM. The name of this website record can be specified at the project node in the Solution Explorer:

The rest of this post discusses the deployment behaviour for each artefact type.

Web pages

Web page items should be placed in the PortalWebPages folder at the root level of the Visual Studio project. The tool allows you to update the following fields of the corresponding web page record in CRM with content from files in Visual Studio:

  • Copy (HTML)
  • Custom JavaScript
  • Custom CSS

The filename (without extension) in Visual Studio determines the target web page record. The file extension determines the field to update using the following rules:

  • .html or .htm:   content of file will be used for the Copy (HTML) field
  • .js:   content of file will be used for the Custom JavaScript field
  • .css:   content of file will be used for the Custom CSS field

So for example you may have the following files to update the Contact Us page:

PortalWebPages\Contact Us.html

PortalWebPages\Contact Us.js

PortalWebPages\Contact Us.css

Not all three files must exist. You can include only those that are required.

The tool does not create new web page records. The web page record must already exist in the target CRM, and must be scoped to the website as identified by the Portal Website Name property of the Visual Studio project.

Web templates

Web template items should be placed in the PortalWebTemplates folder at the root level of the Visual Studio project.

The filename (without extension) in Visual Studio determines the target web template record. While any file extension can be used, the native HTML editor in Visual Studio seems to be the best choice for editing Liquid/HTML, and as such the .html or .htm extension is recommended.

So for example you may have the following file to deploy the Dashboard template:

PortalWebTemplates\Dashboard.html

The tool creates the web template if it does not exist.

Note regarding OnPrem vs Online:

In CRM Portal (Online) web templates must be scoped to a website. OnPrem (7.0.0.25) however does not support this. When the deployment target is CRM Online, the tool will use the website identified by the Portal Website Name project property to search for existing web templates. This setting is ignored when the deployment target is OnPrem.

Entity lists and entity forms

Entity list items should be placed in the PortalEntityLists folder at the root level of the Visual Studio project, and entity forms in the PortalEntityForms folder.

The tool allows you to update the following fields of the corresponding entity list/entity form record in CRM with content from files in Visual Studio:

  • Custom JavaScript

The filename (without extension) in Visual Studio determines the target entity list/entity form record. The file extension determines the field to update using the following rules:

  • .js:   content of file will be used for the Custom JavaScript field

So for example you may have the following file to update the Account List entity list:

PortalEntityLists\Account List.js

And the following file to update the Account Edit entity form:

PortalEntityForms\Account Edit.js

The tool does not create new entity list/entity form records. The entity list/entity form record must already exist in the target CRM.

Note regarding OnPrem vs Online:

In CRM Portal (Online) entity lists and entity forms must be scoped to a website. OnPrem (7.0.0.25) however does not support this. When the deployment target is CRM Online, the tool will use the website identified by the Portal Website Name project property to search for existing entity lists/entity forms. This setting is ignored when the deployment target is OnPrem.

Web form steps

Web form step items should be grouped by the parent web forms, and placed in sub-folders of the PortalWebForms folder, which should sit at the root level of the Visual Studio project. The names of the sub-folders are used to identify the parent web form records in CRM.

For example, the following files hold content for the steps Step 1 and Step 2 of the Registration and Feedback web forms.

PortalWebForms\Registration\Step 1.js

PortalWebForms\Registration\Step 2.js

PortalWebForms\Feedback\Step 1.js

PortalWebForms\Feedback\Step 2.js

The tool allows you to update the following fields of the corresponding web form step record in CRM with content from files in Visual Studio:

  • Custom JavaScript

The filename (without extension) in Visual Studio determines the target web form step record. The file extension determines the field to update using the following rules:

  • .js:   content of file will be used for the Custom JavaScript field

The tool only updates existing web form step records, and does not create new records. It also does not update/create web form records.

Note regarding OnPrem vs Online:

In CRM Portal (Online) web forms must be scoped to a website. OnPrem (7.0.0.25) however does not support this. When the deployment target is CRM Online, the tool will use the website identified by the Portal Website Name project property to search for existing web forms/web form steps. This setting is ignored when the deployment target is OnPrem.

Web files

Web file items should be placed in the PortalWebFiles folder at the root level of the Visual Studio project.

The tool creates new or updates existing web files on the target CRM. You define the web files that should be deployed and their key properties (including Name, Parent Page, Partial Url) in a special XML config file, namely DeploymentSettings.xml.

The DeploymentSettings.xml file should be placed in the PortalWebFiles folder, and the tool can automatically generate a skeleton file for you. When you attempt to deploy a web file, the tool offers to create a skeleton DeploymentSettings.xml file for you if one is not found in the expected location.

Only local files identified in the DeploymentSettings.xml file are eligible for deployment as web files. Below is the generated skeleton file with explanation comments and examples:

<DeploymentSettings>
	<!-- Define a collection of items that will be deployed as Web Files and associated with a particular website. -->
	<WebFiles>
		<!-- Separately define each local files to be deployed as Web Files. -->
<!-- -->
<!-- localPath: Required. Relative to the root PortalWebFiles folder of the project. Value should not start with '\'. -->
<!-- targetName: Optional. The name of the web file once deployed. Default to the same value as partialUrl. -->
<!-- parentPage: Required. The name of the parent web page in CRM. -->
<!-- partialUrl: Optional. The partial URL of the web file once deployed. Default to the file name of the Visual Studio item with all spaces removed. -->
<!-- -->
		<File localPath="common.js" parentPage="Home" />
		<File localPath="scripts\script1.js" parentPage="Home" />
		<File localPath="styles\styles.css" targetName="Portal Styles" parentPage="Home" partialUrl="styles.css"/>

		<!-- Define local files to be deployed as Web files using the folder approach. When this approach is used, default value will be used for targetName and partialUrl for each item under the folder. A Folder element only covers the files that are found directly underneath that folder. Separate Folder elements must be defined for each sub-folders. -->
<!-- -->
<!-- localPath: Required. Relative to the root PortalWebFiles folder of the project. Value should not start with '\'. To refer to files directly underneath the root PortalWebFiles, specify an empty string for this attribute, i.e. localPath="". -->
<!-- parentPage: Required. The name of the parent web page in CRM that will be applied to all items under this folder. -->
<!-- targetNamePrefix: Optional. The specified value will be added to the targetName for all items under this folder. -->
<!-- -->
		<Folder localPath="scripts" parentPage="Home" />
		<Folder localPath="styles" parentPage="Home" targetNamePrefix="My Portal - " />
	</WebFiles>
</DeploymentSettings>

As web files must be scoped to a website, the tool uses the website name as identified by the Portal Website Name project property when searching for existing or creating new web files.

Note regarding file type restriction in CRM:

In Adxstudio/CRM Portal web files are essentially created as attachments to notes. Unless a partialUrl value is defined in DeploymentSettings.xml, the attachment will have the same name as the file in Visual Studio (with spaces removed). CRM however by default blocks certain attachment types, including .js. To enable deployment of .js web files, you will need to unblock this type in CRM.

Conclusion

So there you have it, a new set of commands aimed at Adxstudio/CRM Portal developers to help you work more effectively as a team, and sleep a bit easier at night knowing that you source code are safely tucked away somewhere :).

As always, I would love to hear from you with suggestions and feedback.

 

Posted in Adxstudio, CRM, CRM Portal, CRMQuickDeploy | Leave a comment

Associating multiple external identities to a single contact record in CRM in Adxstudio

In Adxstudio Portal you can associated multiple external identities to a single contact record in CRM. For whatever reason let say contact Bob McCloud has two ADFS accounts with your organisation, contoso\bob1 and contoso\bob2. You can configure it so that regardless of which account is used to login with, the user context is resolved to Bob McCloud.

Contact records in CRM has a grid of External Identities, and this is where the magic happens.

Just add a record to this grid for each external identity that should be mapped to the given contact. As we are using ADFS, we set the Identity Provider field to be the same as the value for the Authentication/WsFederation/ADFS/AuthenticationType Site Setting in CRM.

The user context will now be resolved to be Bob McCloud when one of these identities are used to login.

 

Posted in Adxstudio, CRM, CRM Portal | Leave a comment

How to manually create Contact record for external ID provider in Adxstudio

When configured to use an external ID provider (e.g. ADFS in our case), Adxstudio does not handle identity management functions such as creating user accounts in that external ID provider. When an external ID is authenticated and logged on to the Portal for the first time, Adxstudio automatically creates a new Contact record and associates it with the external ID.

Sometime you may want to customise this process. For example, you may want to do something like this:

  1. Create the Contact record first upon registration request
  2. Email user to confirm email address
  3. Provision user account in ADFS once user has confirmed email address (password may also be provided at this point)
  4. Link Contact record to ADFS account

Step 4 would require you to make appropriate update to the Contact record so that the Adxstudio Portal web app (the MVC app) can establish the link between the Contact record and the currently logon identity.

In order to achieve this you would need to update the following fields on the Contact record:

Field Value
Username (adx_identity_username) The ADFS account username, e.g. mydomain\user1
Login Enabled (adx_identity_logonenabled) True
Security Stamp (adx_identity_securitystamp) A GUID – seems that any GUID will do
Profile Modified On (adx_profilemodifiedon) If a value is not specified, the user will be taken to the Profile page upon login.

You also need to create an External Identity (adx_externalidentity) record and associate it with the Contact.

The fields for this record are:

Field Value
Contact (adx_contactid) The associated Contact record
Username (adx_username) The ADFS account username, e.g. mydomain\user1
Identity Provider (adx_identityprovidername) As we were using ADFS, we set this value to be the same as the value for the Authentication/WsFederation/ADFS/AuthenticationType Site Setting in CRM.

Also note that Adxstudio adds a new form for the Contact entity in CRM, namely Portal Contact. You can use this form to view and update the fields above.

Posted in Adxstudio, CRM Portal | Leave a comment

Does ‘Record status changes’ workflow trigger fire workflow on status change?

A weird post title I know..

One of the most confusing things in CRM is the State and Status (or Status Reason) of a record. These two things are very different in CRM, and it seems that the CRM UI itself often gets mixed up between the two.

When you create a new entity for example, the default label for the statecode field is Status, and for the statuscode field it is Status Reason.

When configuring a workflow, you can specify that it fires on after ‘record status changes‘:

Exactly what does this mean? Will the workflow fire when the record’s State (Active/Inactive) changes? Or will it fire when the record’s Status (‘sub-state’ within Active/Inactive) changes? Will it fire on both?

Well, as it turns out, this option will fire the workflow only when the record’s State changes. Enabling this option has the same effect as enabling the ‘record fields change‘ option and ticking the statecode field in the filter.

In fact, that’s exactly what CRM does behind the scene. Try this:

  • Tick the ‘record status changes‘ option
  • Tick the ‘record fields change‘ option. This enables the Select button to apply field filtering.
  • Now click the Select button. You will see that the statecode field is selected by default.
  • Untick the statecode field from the filter
  • You will see that the ‘record status changes‘ option is now unticked

So in conclusion…

Even though the option say ‘record status changes‘, it really means on record state (Active/Inactive) changes. Misunderstanding this option can cause workflows to fire (or not fire) unexpectedly in your system.

Posted in CRM | Leave a comment

How to get sign-in URL in Adxstudio portal web app for external ID provider

Recently I have been looking at customising the Adxstudio portal web app (the MVC app). One of the things I needed to do was to create a link that would take users to the login page for the portal. Our portal is configured to use ADFS as the external ID provider, and the link needs to take users straight to the ADFS login page.

The Adxstudio framework code has an extension method on the UrlHelper class that allows you to easily do this, namely SignInUrl(). This method accepts an optional returnUrl parameter, which is the URL that users will be redirected to once successfully authenticated.

From the C# code-behind, you can instantiate an instance of UrlHelper and invoke the extension method like so:

lnkLogin.NavigateUrl = new UrlHelper(Request.RequestContext).SignInUrl("/private/dashboard");

From the ASPX markup, you can use the Url property of the page/user-control to invoke the method:

<a href="<%: Url.SignInUrl() %>">Sign in</a>

 

The method uses the authentication settings configured for the website to return the correct sign-in URL for the external ID provider.

Important: This method returns the sign-in URL of the external ID provider only if that provider is configured to be used as the only provider for the website.

You can configure this using the Authentication/Registration/LoginButtonAuthenticationType Site Settings in CRM configuration. For ADFS for example, set the value of this Site Setting to be the same as the Authentication/Registration/LoginButtonAuthenticationType Site Setting.

For more information on these two Site Settings, please see https://docs.microsoft.com/en-us/dynamics365/customer-engagement/portals/set-authentication-identity and https://community.adxstudio.com/products/adxstudio-portals/documentation/configuration-guide/portal-authentication/asp.net-identity-authentication/ws-federation-provider-settings/

 

Posted in Adxstudio, CRM Portal | Leave a comment