diff --git a/FetchXmlBuilder/AppCode/EntityNameItem.cs b/FetchXmlBuilder/AppCode/EntityNameItem.cs new file mode 100644 index 00000000..53c53ecf --- /dev/null +++ b/FetchXmlBuilder/AppCode/EntityNameItem.cs @@ -0,0 +1,25 @@ +using Cinteros.Xrm.XmlEditorUtils; +using Microsoft.Xrm.Sdk.Metadata; + +namespace Cinteros.Xrm.FetchXmlBuilder.AppCode +{ + public class EntityNameItem : IComboBoxItem + { + private EntityMetadata meta = null; + + public EntityNameItem(EntityMetadata Entity) + { + meta = Entity; + } + + public override string ToString() + { + return FetchXmlBuilder.GetEntityDisplayName(meta); + } + + public string GetValue() + { + return meta.ObjectTypeCode.Value.ToString(); + } + } +} \ No newline at end of file diff --git a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs index 07577dba..f6209e67 100644 --- a/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs +++ b/FetchXmlBuilder/AppCode/OData4CodeGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Web; namespace Cinteros.Xrm.FetchXmlBuilder.AppCode { @@ -257,15 +258,31 @@ private static string GetFilter(FetchEntityType entity, FetchXmlBuilder sender, var and = true; foreach (filter filteritem in filteritems) { - resultList.Append(GetFilter(entity.name, filteritem, sender)); + var filterText = GetFilter(entity.name, filteritem, sender); + + if (String.IsNullOrWhiteSpace(filterText)) + { + continue; + } + + if (resultList.Length > 0) + { + resultList.Append(" and "); + } + + resultList.Append(filterText); + if (filteritem.type == filterType.or) and = false; } + var result = resultList.ToString(); - if (result.StartsWith("(") && result.EndsWith(")")) + + if (filteritems.Count == 1 && result.StartsWith("(") && result.EndsWith(")")) { result = result.Substring(1, result.Length - 2); } + if (!String.IsNullOrEmpty(expandFilter)) { if (!and) @@ -352,16 +369,31 @@ private static string GetCondition(string entityName, condition condition, Fetch result += " ne null"; break; case @operator.like: - result = $"contains({navigationProperty + attrMeta.LogicalName}, '{condition.value}')"; - break; case @operator.notlike: - result = $"not contains({navigationProperty + attrMeta.LogicalName}, '{condition.value}')"; + result = $"contains({HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}, {FormatValue(typeof(string), condition.value)})"; + + if (condition.@operator == @operator.notlike) + { + result = "not " + result; + } break; case @operator.beginswith: - result = $"startswith({navigationProperty + attrMeta.LogicalName}, '{condition.value}')"; + case @operator.notbeginwith: + result = $"startswith({HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}, {FormatValue(typeof(string), condition.value)})"; + + if (condition.@operator == @operator.notbeginwith) + { + result = "not " + result; + } break; case @operator.endswith: - result = $"endswith({navigationProperty + attrMeta.LogicalName}, '{condition.value}')"; + case @operator.notendwith: + result = $"endswith({HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}, {FormatValue(typeof(string), condition.value)})"; + + if (condition.@operator == @operator.notendwith) + { + result = "not " + result; + } break; case @operator.above: function = "Above"; @@ -374,7 +406,7 @@ private static string GetCondition(string entityName, condition condition, Fetch functionParameters = Int32.MaxValue; break; case @operator.containvalues: - function = "ContainsValues"; + function = "ContainValues"; functionParameters = Int32.MaxValue; break; case @operator.notcontainvalues: @@ -637,13 +669,13 @@ private static string GetCondition(string entityName, condition condition, Fetch if (!String.IsNullOrEmpty(function)) { if (functionParameters == Int32.MaxValue) - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{navigationProperty + attrMeta.LogicalName}',PropertyValues=[{String.Join(",", condition.Items.Select(i => FormatValue(functionParameterType, i.Value)))}])"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}',PropertyValues=[{String.Join(",", condition.Items.Select(i => FormatValue(functionParameterType, i.Value)))}])"; else if (functionParameters == 0) - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{navigationProperty + attrMeta.LogicalName}')"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}')"; else if (functionParameters == 1) - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{navigationProperty + attrMeta.LogicalName}',PropertyValue={FormatValue(functionParameterType, condition.value)})"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}',PropertyValue={FormatValue(functionParameterType, condition.value)})"; else - return $"Microsoft.Dynamics.CRM.{function}(PropertyName='{navigationProperty + attrMeta.LogicalName}',{String.Join(",", condition.Items.Select((i, idx) => $"Property{idx + 1}={FormatValue(functionParameterType, i.Value)}"))})"; + return $"Microsoft.Dynamics.CRM.{HttpUtility.UrlEncode(function)}(PropertyName='{HttpUtility.UrlEncode(navigationProperty + attrMeta.LogicalName)}',{String.Join(",", condition.Items.Select((i, idx) => $"Property{idx + 1}={FormatValue(functionParameterType, i.Value)}"))})"; } if (!string.IsNullOrEmpty(condition.value) && !result.Contains("(")) @@ -709,7 +741,7 @@ private static string GetPropertyName(AttributeMetadata attr) private static string FormatValue(Type type, string s) { if (type == typeof(string)) - return "'" + s.Replace("'", "''") + "'"; + return "'" + HttpUtility.UrlEncode(s.Replace("'", "''")) + "'"; if (type == typeof(DateTime)) { @@ -727,7 +759,7 @@ private static string FormatValue(Type type, string s) if (type == typeof(Guid)) return Guid.Parse(s).ToString(); - return Convert.ChangeType(s, type).ToString(); + return HttpUtility.UrlEncode(Convert.ChangeType(s, type).ToString()); } private static string GetOrder(FetchEntityType entity, FetchXmlBuilder sender) diff --git a/FetchXmlBuilder/AppCode/OperatorItem.cs b/FetchXmlBuilder/AppCode/OperatorItem.cs index 3d83a47c..e013b7ea 100644 --- a/FetchXmlBuilder/AppCode/OperatorItem.cs +++ b/FetchXmlBuilder/AppCode/OperatorItem.cs @@ -315,7 +315,8 @@ public static OperatorItem[] GetConditionsByAttributeType(AttributeTypeCode valu valueType != AttributeTypeCode.Lookup && valueType != AttributeTypeCode.Customer && valueType != AttributeTypeCode.Owner && - valueType != AttributeTypeCode.Uniqueidentifier) + valueType != AttributeTypeCode.Uniqueidentifier && + valueType != AttributeTypeCode.EntityName) { validConditionsList.Add(new OperatorItem(ConditionOperator.BeginsWith)); validConditionsList.Add(new OperatorItem(ConditionOperator.DoesNotBeginWith)); diff --git a/FetchXmlBuilder/AppCode/TreeNodeHelper.cs b/FetchXmlBuilder/AppCode/TreeNodeHelper.cs index 07200b83..4d7ca143 100644 --- a/FetchXmlBuilder/AppCode/TreeNodeHelper.cs +++ b/FetchXmlBuilder/AppCode/TreeNodeHelper.cs @@ -181,6 +181,7 @@ public static void SetNodeText(TreeNode node, FetchXmlBuilder fxb) { var parent = GetAttributeFromNode(node.Parent.Parent, "name"); attr = fxb.GetAttributeDisplayName(parent, attr); + valueOf = fxb.GetAttributeDisplayName(parent, valueOf); } if (!string.IsNullOrEmpty(ent)) { diff --git a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs index 02cfc46b..650ffead 100644 --- a/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs +++ b/FetchXmlBuilder/Controls/FetchXmlElementControlBase.cs @@ -151,10 +151,15 @@ protected virtual void SaveInternal(bool keyPress) { if (IsInitialized) { - SendSaveMessage(ControlUtils.GetAttributesCollection(this.Controls, true), keyPress); + SendSaveMessage(GetAttributesCollection(), keyPress); } } + protected virtual Dictionary GetAttributesCollection() + { + return ControlUtils.GetAttributesCollection(this.Controls, true); + } + protected virtual ControlValidationResult ValidateControl(Control control) { return null; diff --git a/FetchXmlBuilder/Controls/conditionControl.cs b/FetchXmlBuilder/Controls/conditionControl.cs index f654d1f4..e7db491e 100644 --- a/FetchXmlBuilder/Controls/conditionControl.cs +++ b/FetchXmlBuilder/Controls/conditionControl.cs @@ -1,531 +1,646 @@ -using Cinteros.Xrm.FetchXmlBuilder.AppCode; -using Cinteros.Xrm.FetchXmlBuilder.DockControls; -using Cinteros.Xrm.XmlEditorUtils; -using Microsoft.Xrm.Sdk; -using Microsoft.Xrm.Sdk.Metadata; -using Microsoft.Xrm.Sdk.Query; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.ServiceModel; -using System.Windows.Forms; -using xrmtb.XrmToolBox.Controls; - -namespace Cinteros.Xrm.FetchXmlBuilder.Controls -{ - public partial class conditionControl : FetchXmlElementControlBase - { - #region Public Constructors - - public conditionControl() : this(null, null, null) - { - } - - public conditionControl(TreeNode node, FetchXmlBuilder fetchXmlBuilder, TreeBuilderControl tree) - { - InitializeComponent(); - BeginInit(); - txtLookup.OrganizationService = fetchXmlBuilder.Service; - dlgLookup.Service = fetchXmlBuilder.Service; - rbUseLookup.Checked = fetchXmlBuilder.settings.UseLookup; - rbEnterGuid.Checked = !rbUseLookup.Checked; - InitializeFXB(null, fetchXmlBuilder, tree, node); - EndInit(); - RefreshAttributes(); - } - - #endregion Public Constructors - - #region Protected Methods - - protected override void PopulateControls() - { - cmbEntity.Items.Clear(); - var closestEntity = GetClosestEntityNode(Node); - if (closestEntity != null && closestEntity.Name == "entity") - { - cmbEntity.Items.Add(""); - cmbEntity.Items.AddRange(GetEntities(Tree.tvFetch.Nodes[0]).ToArray()); - } - cmbEntity.Enabled = cmbEntity.Items.Count > 0; - cmbOperator.Items.Clear(); - foreach (var oper in Enum.GetValues(typeof(ConditionOperator))) - { - cmbOperator.Items.Add(new OperatorItem((ConditionOperator)oper)); - } - } - - protected override bool RequiresSave() - { - return base.RequiresSave() || - cmbOperator.SelectedItem is OperatorItem op && op.IsMultipleValuesType && !string.IsNullOrEmpty(cmbValue.Text); - } - - protected override void SaveInternal(bool silent) - { - if (!silent && cmbOperator.SelectedItem != null && cmbOperator.SelectedItem is OperatorItem) - { - ExtractCommaSeparatedValues(); - } - - base.SaveInternal(silent); - } - - protected override ControlValidationResult ValidateControl(Control control) - { - if (control == cmbAttribute) - { - if (string.IsNullOrWhiteSpace(cmbAttribute.Text)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Attribute is required"); - } - - if (fxb.Service != null && !cmbAttribute.Items.OfType().Any(i => i.ToString() == cmbAttribute.Text)) - { - return new ControlValidationResult(ControlValidationLevel.Warning, "Attribute is not valid"); - } - } - else if (control == cmbOperator || control == cmbValue) - { - if (control == cmbOperator && string.IsNullOrWhiteSpace(cmbOperator.Text)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator is required"); - } - - if (control == cmbOperator && !cmbOperator.Items.OfType().Any(i => i.ToString() == cmbOperator.Text)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator is not valid"); - } - - if (cmbOperator.SelectedItem != null && cmbOperator.SelectedItem is OperatorItem oper && (!oper.IsMultipleValuesType || Node.Nodes.Count > 0)) - { - AttributeItem attribute = null; - if (cmbAttribute.SelectedItem != null && cmbAttribute.SelectedItem is AttributeItem) - { // Get type from condition attribute - attribute = (AttributeItem)cmbAttribute.SelectedItem; - } - var valueType = oper.ValueType; - var attributeType = oper.AttributeType; - var value = ControlUtils.GetValueFromControl(cmbValue).Trim(); - if (valueType == AttributeTypeCode.ManagedProperty) - { // Type not defined by operator - if (attribute != null) - { // Get type from condition attribute - valueType = attribute.Metadata.AttributeType; - } - else - { // Default, cannot determine type - valueType = AttributeTypeCode.String; - } - } - - if (attributeType != null && attribute != null && control == cmbOperator) - { - if (attributeType != attribute.Metadata.AttributeType) - { - // Some attribute type combinations are ok - if (attributeType == AttributeTypeCode.String && attribute.Metadata.AttributeType == AttributeTypeCode.Memo) - { - // This is ok - } - else if (attributeType == AttributeTypeCode.Lookup && attribute.Metadata.AttributeType == AttributeTypeCode.Owner) - { - // This is ok - } - else if (attributeType == AttributeTypeCode.Lookup && attribute.Metadata.AttributeType == AttributeTypeCode.Customer) - { - // This is ok - } - else if (attributeType == AttributeTypeCode.Lookup && attribute.Metadata.AttributeType == AttributeTypeCode.Uniqueidentifier) - { - // This is also ok - } - else - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " is not valid for attribute of type " + attribute.Metadata.AttributeType.ToString()); - } - } - } - - if (control == cmbValue) - { - if (!string.IsNullOrWhiteSpace(cmbValueOf.Text) && !string.IsNullOrWhiteSpace(cmbValue.Text)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Value and Value Of cannot both be set"); - } - - switch (valueType) - { - case null: - if (!string.IsNullOrWhiteSpace(value)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " does not allow value"); - } - break; - case AttributeTypeCode.Boolean: - if (value != "0" && value != "1") - { - return new ControlValidationResult(ControlValidationLevel.Error, "Value must be 0 or 1"); - } - break; - case AttributeTypeCode.DateTime: - DateTime date; - if (!DateTime.TryParse(value, out date)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires date value"); - } - break; - case AttributeTypeCode.Integer: - case AttributeTypeCode.State: - case AttributeTypeCode.Status: - case AttributeTypeCode.Picklist: - case AttributeTypeCode.BigInt: - int intvalue; - if (!int.TryParse(value, out intvalue)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires whole number value"); - } - break; - case AttributeTypeCode.Decimal: - case AttributeTypeCode.Double: - case AttributeTypeCode.Money: - decimal decvalue; - if (!decimal.TryParse(value, out decvalue)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires decimal value"); - } - break; - case AttributeTypeCode.Lookup: - case AttributeTypeCode.Customer: - case AttributeTypeCode.Owner: - case AttributeTypeCode.Uniqueidentifier: - Guid guidvalue; - if (!Guid.TryParse(value, out guidvalue)) - { - return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires a proper guid with format: " + Guid.Empty.ToString()); - } - break; - case AttributeTypeCode.String: - case AttributeTypeCode.Memo: - case AttributeTypeCode.EntityName: - case AttributeTypeCode.Virtual: - break; - case AttributeTypeCode.PartyList: - case AttributeTypeCode.CalendarRules: - //case AttributeTypeCode.ManagedProperty: // ManagedProperty is a bit "undefined", so let's accept all values for now... ref issue #67 - return new ControlValidationResult(ControlValidationLevel.Error, "Unsupported condition attribute type: " + valueType); - } - } - } - } - - return base.ValidateControl(control); - } - - #endregion Protected Methods - - #region Private Methods - - private static TreeNode GetClosestEntityNode(TreeNode node) - { - var parentNode = node.Parent; - while (parentNode != null && parentNode.Name != "entity" && parentNode.Name != "link-entity") - { - parentNode = parentNode.Parent; - } - return parentNode; - } - - private void ExtractCommaSeparatedValues() - { - var oper = (OperatorItem)cmbOperator.SelectedItem; - if (oper.IsMultipleValuesType && !string.IsNullOrWhiteSpace(cmbValue.Text)) - { - // Now we need to generate value nodes under this node instead of just adding the value - foreach (var valuestr in cmbValue.Text.Split(',')) - { - var value = valuestr.Trim(); - var attrNode = TreeNodeHelper.AddChildNode(Node, "value"); - var coll = new Dictionary(); - coll.Add("#text", value); - attrNode.Tag = coll; - TreeNodeHelper.SetNodeText(attrNode, fxb); - } - cmbValue.Text = ""; - } - } - - private List GetEntities(TreeNode node) - { - var result = new List(); - if (node.Name == "link-entity") - { - result.Add(new EntityNode(node)); - } - foreach (TreeNode child in node.Nodes) - { - result.AddRange(GetEntities(child)); - } - return result; - } - - private void RefreshAttributes() - { - if (!IsInitialized) - { - return; - } - cmbAttribute.Items.Clear(); - var entityNode = cmbEntity.SelectedItem is EntityNode ? (EntityNode)cmbEntity.SelectedItem : null; - if (entityNode == null) - { - entityNode = new EntityNode(GetClosestEntityNode(Node)); - } - if (entityNode == null) - { - return; - } - var entityName = entityNode.EntityName; - if (fxb.NeedToLoadEntity(entityName)) - { - if (!fxb.working) - { - fxb.LoadEntityDetails(entityName, RefreshAttributes); - } - return; - } - BeginInit(); - var attributes = fxb.GetDisplayAttributes(entityName); - attributes.ToList().ForEach(a => AttributeItem.AddAttributeToComboBox(cmbAttribute, a, true, FetchXmlBuilder.friendlyNames)); - // RefreshFill now that attributes are loaded - ReFillControl(cmbAttribute); - ReFillControl(cmbValue); - ReFillControl(cmbValueOf); - EndInit(); - RefreshOperators(); - UpdateValueField(); - } - - private void RefreshOperators() - { - if (!IsInitialized) - { - return; - } - if (cmbAttribute.SelectedItem is AttributeItem attributeItem && attributeItem.Metadata.AttributeType is AttributeTypeCode attributeType) - { - //cmbOperator.SelectedItem = null; - cmbOperator.Items.Clear(); - cmbOperator.Items.AddRange(OperatorItem.GetConditionsByAttributeType(attributeType, attributeItem.Metadata.AttributeTypeName?.Value)); - ReFillControl(cmbOperator); - } - } - - private void UpdateValueField() - { - if (!IsInitialized) - { - return; - } - panValue.Visible = true; - panValueOf.Visible = false; - panValueLookup.Visible = false; - panGuidSelector.Visible = false; - cmbValue.Items.Clear(); - cmbValue.DropDownStyle = ComboBoxStyle.Simple; - lblValueHint.Visible = false; - if (cmbOperator.SelectedItem == null || !(cmbOperator.SelectedItem is OperatorItem oper)) - { - return; - } - var valueType = oper.ValueType; - var attribute = cmbAttribute.SelectedItem as AttributeItem; - if (valueType == AttributeTypeCode.ManagedProperty && attribute != null) - { // Indicates value type is determined by selected attribute - valueType = attribute.Metadata.AttributeType; - if (oper.IsMultipleValuesType) - { - if (Node.Nodes.Count == 0) - { - lblValueHint.Text = "Enter comma-separated " + valueType.ToString() + " values or add sub-nodes."; - lblValueHint.Visible = true; - } - else - { - valueType = null; - } - } - else if (attribute.Metadata is EnumAttributeMetadata enummeta && - enummeta.OptionSet is OptionSetMetadata options && - !(attribute.Metadata is EntityNameAttributeMetadata)) - { - cmbValue.Items.AddRange(options.Options.Select(o => new OptionsetItem(o)).ToArray()); - cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; - } - else if (attribute.Metadata is LookupAttributeMetadata lookupmeta) - { - if (fxb.settings.UseLookup) - { - if (Guid.TryParse(cmbValue.Text, out Guid id) && !Guid.Empty.Equals(id)) - { - var loookuptargets = new List(); - if (!string.IsNullOrWhiteSpace(txtUitype.Text)) - { - loookuptargets.Add(txtUitype.Text.Trim()); - } - else - { - loookuptargets.AddRange(lookupmeta.Targets); - } - foreach (var target in loookuptargets) - { - try - { - txtLookup.LogicalName = target; - txtLookup.Id = id; - txtUitype.Text = target; - break; - } - catch (FaultException) - { - // really nothing to do here, loading the record is simply nice to have - } - } - } - } - else - { - txtUitype.Text = string.Empty; - txtLookup.Text = string.Empty; - } - } - } - if (valueType == null) - { - cmbValue.Text = ""; - cmbValue.Enabled = false; - } - else - { - cmbValue.Enabled = true; - - if (cmbValue.Items.Count > 0 && cmbValue.SelectedIndex == -1 && !string.IsNullOrWhiteSpace(cmbValue.Text)) - { - var item = cmbValue.Items.OfType().FirstOrDefault(i => i.ToString() == cmbValue.Text); - cmbValue.SelectedItem = item; - } - } - if (valueType == AttributeTypeCode.Lookup || - valueType == AttributeTypeCode.Customer || - valueType == AttributeTypeCode.Owner || - valueType == AttributeTypeCode.Uniqueidentifier) - { - dlgLookup.LogicalNames = null; - if (attribute?.Metadata is LookupAttributeMetadata lookupmeta) - { - dlgLookup.LogicalNames = lookupmeta.Targets; - } - else if (attribute?.Metadata is AttributeMetadata attrmeta && attrmeta.IsPrimaryId == true) - { - if (attrmeta.IsLogical == false) - { - var entitynode = new EntityNode(GetClosestEntityNode(Node)); - dlgLookup.LogicalName = entitynode.EntityName; - } - else if (attrmeta.LogicalName.EndsWith("addressid")) - { - dlgLookup.LogicalName = "customeraddress"; - } - } - rbUseLookup.Enabled = dlgLookup.LogicalNames?.Length > 0; - if (!rbUseLookup.Enabled) - { - rbEnterGuid.Checked = true; - } - if (string.IsNullOrWhiteSpace(cmbValue.Text)) - { - cmbValue.Text = Guid.Empty.ToString(); - } - panGuidSelector.Visible = true; - panValue.Visible = !rbUseLookup.Checked; - panValueLookup.Visible = rbUseLookup.Checked; - } - - if (oper.SupportsColumnComparison && !(cmbEntity.SelectedItem is EntityNode)) - { - panValueOf.Visible = true; - cmbValueOf.Items.Clear(); - if (attribute != null) - { - foreach (AttributeItem item in cmbAttribute.Items) - { - if (item.Metadata.AttributeType == attribute.Metadata.AttributeType) - { - cmbValueOf.Items.Add(new AttributeItem(item.Metadata)); - } - } - } - } - - if (!panValueOf.Visible) - { - cmbValueOf.Text = ""; - } - } - - #endregion Private Methods - - #region Private Event Handlers - - private void btnLookup_Click(object sender, EventArgs e) - { - Cursor = Cursors.WaitCursor; - switch (dlgLookup.ShowDialog(this)) - { - case DialogResult.OK: - txtLookup.Entity = dlgLookup.Entity; - txtUitype.Text = dlgLookup.Entity.LogicalName; - break; - case DialogResult.Abort: - txtLookup.Entity = null; - break; - } - cmbValue.Text = (txtLookup?.Entity?.Id ?? Guid.Empty).ToString(); - Cursor = Cursors.Default; - } - - private void cmbAttribute_SelectedIndexChanged(object sender, EventArgs e) - { - RefreshOperators(); - } - - private void cmbEntity_SelectedIndexChanged(object sender, EventArgs e) - { - RefreshAttributes(); - } - - private void cmbOperator_SelectedIndexChanged(object sender, EventArgs e) - { - UpdateValueField(); - } - - private void rbUseLookup_CheckedChanged(object sender, EventArgs e) - { - UpdateValueField(); - } - - private void txtLookup_RecordClick(object sender, CDSRecordEventArgs e) - { - var url = fxb.ConnectionDetail.GetEntityUrl(e.Entity); - url = fxb.ConnectionDetail.GetEntityReferenceUrl(txtLookup.EntityReference); - if (!string.IsNullOrEmpty(url)) - { - fxb.LogUse("OpenRecord"); - Process.Start(url); - } - } - - #endregion Private Event Handlers - } +using Cinteros.Xrm.FetchXmlBuilder.AppCode; +using Cinteros.Xrm.FetchXmlBuilder.DockControls; +using Cinteros.Xrm.XmlEditorUtils; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Metadata; +using Microsoft.Xrm.Sdk.Query; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.ServiceModel; +using System.Windows.Forms; +using xrmtb.XrmToolBox.Controls; + +namespace Cinteros.Xrm.FetchXmlBuilder.Controls +{ + public partial class conditionControl : FetchXmlElementControlBase + { + #region Public Constructors + + public conditionControl() : this(null, null, null) + { + } + + public conditionControl(TreeNode node, FetchXmlBuilder fetchXmlBuilder, TreeBuilderControl tree) + { + InitializeComponent(); + BeginInit(); + txtLookup.OrganizationService = fetchXmlBuilder.Service; + dlgLookup.Service = fetchXmlBuilder.Service; + rbUseLookup.Checked = fetchXmlBuilder.settings.UseLookup; + rbEnterGuid.Checked = !rbUseLookup.Checked; + InitializeFXB(null, fetchXmlBuilder, tree, node); + EndInit(); + RefreshAttributes(); + } + + #endregion Public Constructors + + #region Protected Methods + + protected override void PopulateControls() + { + cmbEntity.Items.Clear(); + var closestEntity = GetClosestEntityNode(Node); + if (closestEntity != null && closestEntity.Name == "entity") + { + cmbEntity.Items.Add(""); + cmbEntity.Items.AddRange(GetEntities(Tree.tvFetch.Nodes[0]).ToArray()); + } + cmbEntity.Enabled = cmbEntity.Items.Count > 0; + cmbOperator.Items.Clear(); + foreach (var oper in Enum.GetValues(typeof(ConditionOperator))) + { + cmbOperator.Items.Add(new OperatorItem((ConditionOperator)oper)); + } + } + + protected override bool RequiresSave() + { + return base.RequiresSave() || + cmbOperator.SelectedItem is OperatorItem op && op.IsMultipleValuesType && !string.IsNullOrEmpty(cmbValue.Text); + } + + protected override void SaveInternal(bool silent) + { + if (!silent && cmbOperator.SelectedItem != null && cmbOperator.SelectedItem is OperatorItem) + { + ExtractCommaSeparatedValues(); + } + + base.SaveInternal(silent); + } + + protected override Dictionary GetAttributesCollection() + { + var attr = base.GetAttributesCollection(); + + if (attr.TryGetValue("operator", out var op) && attr.TryGetValue("value", out var value)) + { + if (op == "begins-with") + { + attr["operator"] = "like"; + attr["value"] = value + "%"; + } + else if (op == "not-begin-with") + { + attr["operator"] = "not-like"; + attr["value"] = value + "%"; + } + else if (op == "ends-with") + { + attr["operator"] = "like"; + attr["value"] = "%" + value; + } + else if (op == "not-end-with") + { + attr["operator"] = "not-like"; + attr["value"] = "%" + value; + } + else if (op == "contains") + { + attr["operator"] = "like"; + attr["value"] = "%" + value + "%"; + } + else if (op == "does-not-contain") + { + attr["operator"] = "not-like"; + attr["value"] = "%" + value + "%"; + } + } + + return attr; + } + + protected override ControlValidationResult ValidateControl(Control control) + { + if (control == cmbAttribute) + { + if (string.IsNullOrWhiteSpace(cmbAttribute.Text)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Attribute is required"); + } + + if (fxb.Service != null && !cmbAttribute.Items.OfType().Any(i => i.ToString() == cmbAttribute.Text)) + { + return new ControlValidationResult(ControlValidationLevel.Warning, "Attribute is not valid"); + } + } + else if (control == cmbOperator || control == cmbValue) + { + if (control == cmbOperator && string.IsNullOrWhiteSpace(cmbOperator.Text)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator is required"); + } + + if (control == cmbOperator && !cmbOperator.Items.OfType().Any(i => i.ToString() == cmbOperator.Text)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator is not valid"); + } + + if (cmbOperator.SelectedItem != null && cmbOperator.SelectedItem is OperatorItem oper && (!oper.IsMultipleValuesType || Node.Nodes.Count > 0)) + { + AttributeItem attribute = null; + if (cmbAttribute.SelectedItem != null && cmbAttribute.SelectedItem is AttributeItem) + { // Get type from condition attribute + attribute = (AttributeItem)cmbAttribute.SelectedItem; + } + var valueType = oper.ValueType; + var attributeType = oper.AttributeType; + var value = ControlUtils.GetValueFromControl(cmbValue).Trim(); + if (valueType == AttributeTypeCode.ManagedProperty) + { // Type not defined by operator + if (attribute != null) + { // Get type from condition attribute + valueType = attribute.Metadata.AttributeType; + } + else + { // Default, cannot determine type + valueType = AttributeTypeCode.String; + } + } + + if (attributeType != null && attribute != null && control == cmbOperator) + { + if (attributeType != attribute.Metadata.AttributeType) + { + // Some attribute type combinations are ok + if (attributeType == AttributeTypeCode.String && attribute.Metadata.AttributeType == AttributeTypeCode.Memo) + { + // This is ok + } + else if (attributeType == AttributeTypeCode.Lookup && attribute.Metadata.AttributeType == AttributeTypeCode.Owner) + { + // This is ok + } + else if (attributeType == AttributeTypeCode.Lookup && attribute.Metadata.AttributeType == AttributeTypeCode.Customer) + { + // This is ok + } + else if (attributeType == AttributeTypeCode.Lookup && attribute.Metadata.AttributeType == AttributeTypeCode.Uniqueidentifier) + { + // This is also ok + } + else + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " is not valid for attribute of type " + attribute.Metadata.AttributeType.ToString()); + } + } + } + + if (control == cmbValue) + { + if (!string.IsNullOrWhiteSpace(cmbValueOf.Text) && !string.IsNullOrWhiteSpace(cmbValue.Text)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Value and Value Of cannot both be set"); + } + + if (!string.IsNullOrWhiteSpace(cmbValueOf.Text)) + { + return null; + } + + switch (valueType) + { + case null: + if (!string.IsNullOrWhiteSpace(value)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " does not allow value"); + } + break; + case AttributeTypeCode.Boolean: + if (value != "0" && value != "1") + { + return new ControlValidationResult(ControlValidationLevel.Error, "Value must be 0 or 1"); + } + break; + case AttributeTypeCode.DateTime: + DateTime date; + if (!DateTime.TryParse(value, out date)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires date value"); + } + break; + case AttributeTypeCode.Integer: + case AttributeTypeCode.State: + case AttributeTypeCode.Status: + case AttributeTypeCode.Picklist: + case AttributeTypeCode.BigInt: + case AttributeTypeCode.EntityName: + int intvalue; + if (!int.TryParse(value, out intvalue)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires whole number value"); + } + break; + case AttributeTypeCode.Decimal: + case AttributeTypeCode.Double: + case AttributeTypeCode.Money: + decimal decvalue; + if (!decimal.TryParse(value, out decvalue)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires decimal value"); + } + break; + case AttributeTypeCode.Lookup: + case AttributeTypeCode.Customer: + case AttributeTypeCode.Owner: + case AttributeTypeCode.Uniqueidentifier: + Guid guidvalue; + if (!Guid.TryParse(value, out guidvalue)) + { + return new ControlValidationResult(ControlValidationLevel.Error, "Operator " + oper.ToString() + " requires a proper guid with format: " + Guid.Empty.ToString()); + } + break; + case AttributeTypeCode.String: + case AttributeTypeCode.Memo: + case AttributeTypeCode.Virtual: + break; + case AttributeTypeCode.PartyList: + case AttributeTypeCode.CalendarRules: + //case AttributeTypeCode.ManagedProperty: // ManagedProperty is a bit "undefined", so let's accept all values for now... ref issue #67 + return new ControlValidationResult(ControlValidationLevel.Error, "Unsupported condition attribute type: " + valueType); + } + } + } + } + + return base.ValidateControl(control); + } + + #endregion Protected Methods + + #region Private Methods + + private static TreeNode GetClosestEntityNode(TreeNode node) + { + var parentNode = node.Parent; + while (parentNode != null && parentNode.Name != "entity" && parentNode.Name != "link-entity") + { + parentNode = parentNode.Parent; + } + return parentNode; + } + + private void ExtractCommaSeparatedValues() + { + var oper = (OperatorItem)cmbOperator.SelectedItem; + if (oper.IsMultipleValuesType && !string.IsNullOrWhiteSpace(cmbValue.Text)) + { + // Now we need to generate value nodes under this node instead of just adding the value + foreach (var valuestr in cmbValue.Text.Split(',')) + { + var value = valuestr.Trim(); + var attrNode = TreeNodeHelper.AddChildNode(Node, "value"); + var coll = new Dictionary(); + coll.Add("#text", value); + attrNode.Tag = coll; + TreeNodeHelper.SetNodeText(attrNode, fxb); + } + cmbValue.Text = ""; + } + } + + private List GetEntities(TreeNode node) + { + var result = new List(); + if (node.Name == "link-entity") + { + result.Add(new EntityNode(node)); + } + foreach (TreeNode child in node.Nodes) + { + result.AddRange(GetEntities(child)); + } + return result; + } + + private void RefreshAttributes() + { + if (!IsInitialized) + { + return; + } + cmbAttribute.Items.Clear(); + var entityNode = cmbEntity.SelectedItem is EntityNode ? (EntityNode)cmbEntity.SelectedItem : null; + if (entityNode == null) + { + entityNode = new EntityNode(GetClosestEntityNode(Node)); + } + if (entityNode == null) + { + return; + } + var entityName = entityNode.EntityName; + if (fxb.NeedToLoadEntity(entityName)) + { + if (!fxb.working) + { + fxb.LoadEntityDetails(entityName, RefreshAttributes); + } + return; + } + BeginInit(); + var attributes = fxb.GetDisplayAttributes(entityName); + attributes.ToList().ForEach(a => AttributeItem.AddAttributeToComboBox(cmbAttribute, a, true, FetchXmlBuilder.friendlyNames)); + // RefreshFill now that attributes are loaded + ReFillControl(cmbAttribute); + ReFillControl(cmbValue); + EndInit(); + RefreshOperators(); + UpdateValueField(); + NormalizeLike(); + ReFillControl(cmbValueOf); + } + + private void NormalizeLike() + { + var op = cmbOperator.SelectedItem as OperatorItem; + + if (op == null) + { + return; + } + + var value = cmbValue.Text; + + if (String.IsNullOrWhiteSpace(value)) + { + return; + } + + if (op.GetValue() == "like" || op.GetValue() == "not-like") + { + string newOp = null; + + if (value.StartsWith("%") && value.EndsWith("%") && value.Length >= 2) + { + newOp = "contains"; + value = value.Substring(1, value.Length - 2); + } + else if (value.StartsWith("%")) + { + newOp = "ends-with"; + value = value.Substring(1); + } + else if (value.EndsWith("%")) + { + newOp = "begins-with"; + value = value.Substring(0, value.Length - 1); + } + + if (newOp != null) + { + if (op.GetValue() == "not-like") + { + newOp = "not-" + newOp.Replace("s-", "-"); + + if (newOp == "not-contains") + { + newOp = "does-not-contain"; + } + } + + var newOpItem = cmbOperator.Items.OfType().FirstOrDefault(o => o.GetValue() == newOp); + + if (newOpItem != null) + { + cmbOperator.SelectedItem = newOpItem; + cmbValue.Text = value; + } + } + } + } + + private void RefreshOperators() + { + if (!IsInitialized) + { + return; + } + if (cmbAttribute.SelectedItem is AttributeItem attributeItem && attributeItem.Metadata.AttributeType is AttributeTypeCode attributeType) + { + //cmbOperator.SelectedItem = null; + cmbOperator.Items.Clear(); + cmbOperator.Items.AddRange(OperatorItem.GetConditionsByAttributeType(attributeType, attributeItem.Metadata.AttributeTypeName?.Value)); + ReFillControl(cmbOperator); + } + } + + private void UpdateValueField() + { + if (!IsInitialized) + { + return; + } + panValue.Visible = true; + panValueOf.Visible = false; + panValueLookup.Visible = false; + panGuidSelector.Visible = false; + cmbValue.Items.Clear(); + cmbValue.DropDownStyle = ComboBoxStyle.Simple; + lblValueHint.Visible = false; + if (cmbOperator.SelectedItem == null || !(cmbOperator.SelectedItem is OperatorItem oper)) + { + return; + } + var valueType = oper.ValueType; + var attribute = cmbAttribute.SelectedItem as AttributeItem; + if (valueType == AttributeTypeCode.ManagedProperty && attribute != null) + { // Indicates value type is determined by selected attribute + valueType = attribute.Metadata.AttributeType; + if (oper.IsMultipleValuesType) + { + if (Node.Nodes.Count == 0) + { + lblValueHint.Text = "Enter comma-separated " + valueType.ToString() + " values or add sub-nodes."; + lblValueHint.Visible = true; + } + else + { + valueType = null; + } + } + else if (attribute.Metadata is EnumAttributeMetadata enummeta && + enummeta.OptionSet is OptionSetMetadata options && + !(attribute.Metadata is EntityNameAttributeMetadata)) + { + cmbValue.Items.AddRange(options.Options.Select(o => new OptionsetItem(o)).ToArray()); + var value = cmbValue.Text; + cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; + cmbValue.SelectedItem = cmbValue.Items.OfType().FirstOrDefault(i => i.GetValue() == value); + } + else if (attribute.Metadata is EntityNameAttributeMetadata) + { + var entities = fxb.GetDisplayEntities(); + if (entities != null) + { + cmbValue.Items.AddRange(entities.Select(e => new EntityNameItem(e.Value)).ToArray()); + var value = cmbValue.Text; + cmbValue.DropDownStyle = ComboBoxStyle.DropDownList; + cmbValue.SelectedItem = cmbValue.Items.OfType().FirstOrDefault(i => i.GetValue() == value); + } + } + else if (attribute.Metadata is LookupAttributeMetadata lookupmeta) + { + if (fxb.settings.UseLookup) + { + if (Guid.TryParse(cmbValue.Text, out Guid id) && !Guid.Empty.Equals(id)) + { + var loookuptargets = new List(); + if (!string.IsNullOrWhiteSpace(txtUitype.Text)) + { + loookuptargets.Add(txtUitype.Text.Trim()); + } + else + { + loookuptargets.AddRange(lookupmeta.Targets); + } + foreach (var target in loookuptargets) + { + try + { + txtLookup.LogicalName = target; + txtLookup.Id = id; + txtUitype.Text = target; + break; + } + catch (FaultException) + { + // really nothing to do here, loading the record is simply nice to have + } + } + } + } + else + { + txtUitype.Text = string.Empty; + txtLookup.Text = string.Empty; + } + } + } + + if (valueType == null) + { + cmbValue.Text = ""; + cmbValue.Enabled = false; + } + else + { + cmbValue.Enabled = true; + } + + if (valueType == AttributeTypeCode.Lookup || + valueType == AttributeTypeCode.Customer || + valueType == AttributeTypeCode.Owner || + valueType == AttributeTypeCode.Uniqueidentifier) + { + dlgLookup.LogicalNames = null; + if (attribute?.Metadata is LookupAttributeMetadata lookupmeta) + { + dlgLookup.LogicalNames = lookupmeta.Targets; + } + else if (attribute?.Metadata is AttributeMetadata attrmeta && attrmeta.IsPrimaryId == true) + { + if (attrmeta.IsLogical == false) + { + var entitynode = new EntityNode(GetClosestEntityNode(Node)); + dlgLookup.LogicalName = entitynode.EntityName; + } + else if (attrmeta.LogicalName.EndsWith("addressid")) + { + dlgLookup.LogicalName = "customeraddress"; + } + } + rbUseLookup.Enabled = dlgLookup.LogicalNames?.Length > 0; + if (!rbUseLookup.Enabled) + { + rbEnterGuid.Checked = true; + } + if (string.IsNullOrWhiteSpace(cmbValue.Text)) + { + cmbValue.Text = Guid.Empty.ToString(); + } + panGuidSelector.Visible = true; + panValue.Visible = !rbUseLookup.Checked; + panValueLookup.Visible = rbUseLookup.Checked; + } + + if (oper.SupportsColumnComparison && !(cmbEntity.SelectedItem is EntityNode)) + { + panValueOf.Visible = true; + cmbValueOf.Items.Clear(); + if (attribute != null) + { + foreach (AttributeItem item in cmbAttribute.Items) + { + if (item.Metadata.AttributeType == attribute.Metadata.AttributeType) + { + cmbValueOf.Items.Add(new AttributeItem(item.Metadata)); + } + } + } + } + + if (!panValueOf.Visible) + { + cmbValueOf.Text = ""; + } + } + + #endregion Private Methods + + #region Private Event Handlers + + private void btnLookup_Click(object sender, EventArgs e) + { + Cursor = Cursors.WaitCursor; + switch (dlgLookup.ShowDialog(this)) + { + case DialogResult.OK: + txtLookup.Entity = dlgLookup.Entity; + txtUitype.Text = dlgLookup.Entity.LogicalName; + break; + case DialogResult.Abort: + txtLookup.Entity = null; + break; + } + cmbValue.Text = (txtLookup?.Entity?.Id ?? Guid.Empty).ToString(); + Cursor = Cursors.Default; + } + + private void cmbAttribute_SelectedIndexChanged(object sender, EventArgs e) + { + RefreshOperators(); + } + + private void cmbEntity_SelectedIndexChanged(object sender, EventArgs e) + { + RefreshAttributes(); + } + + private void cmbOperator_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateValueField(); + } + + private void rbUseLookup_CheckedChanged(object sender, EventArgs e) + { + UpdateValueField(); + } + + private void txtLookup_RecordClick(object sender, CDSRecordEventArgs e) + { + var url = fxb.ConnectionDetail.GetEntityUrl(e.Entity); + url = fxb.ConnectionDetail.GetEntityReferenceUrl(txtLookup.EntityReference); + if (!string.IsNullOrEmpty(url)) + { + fxb.LogUse("OpenRecord"); + Process.Start(url); + } + } + + #endregion Private Event Handlers + } } \ No newline at end of file diff --git a/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs b/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs index a58d193e..a167889a 100644 --- a/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs +++ b/FetchXmlBuilder/DockControls/FlowListControl.Designer.cs @@ -105,6 +105,7 @@ private void InitializeComponent() this.linkSelect.TabStop = true; this.linkSelect.Tag = "Select formula"; this.linkSelect.Text = "N/A"; + this.linkSelect.UseMnemonic = false; this.linkSelect.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // lblCopied @@ -141,6 +142,7 @@ private void InitializeComponent() this.linkExpand.TabStop = true; this.linkExpand.Tag = "Expand Query"; this.linkExpand.Text = "N/A"; + this.linkExpand.UseMnemonic = false; this.linkExpand.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // linkTop @@ -156,6 +158,7 @@ private void InitializeComponent() this.linkTop.TabStop = true; this.linkTop.Tag = "Top Count"; this.linkTop.Text = "N/A"; + this.linkTop.UseMnemonic = false; this.linkTop.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // linkOrder @@ -171,6 +174,7 @@ private void InitializeComponent() this.linkOrder.TabStop = true; this.linkOrder.Tag = "Order By"; this.linkOrder.Text = "N/A"; + this.linkOrder.UseMnemonic = false; this.linkOrder.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // linkFilter @@ -186,6 +190,7 @@ private void InitializeComponent() this.linkFilter.TabStop = true; this.linkFilter.Tag = "Filter Query"; this.linkFilter.Text = "N/A"; + this.linkFilter.UseMnemonic = false; this.linkFilter.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LinkLabel_LinkClicked); // // label5 diff --git a/FetchXmlBuilder/FetchXmlBuilder.csproj b/FetchXmlBuilder/FetchXmlBuilder.csproj index 91ee292e..3216be08 100644 --- a/FetchXmlBuilder/FetchXmlBuilder.csproj +++ b/FetchXmlBuilder/FetchXmlBuilder.csproj @@ -62,7 +62,7 @@ ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.5.2.8\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.49\lib\net462\Microsoft.Rest.ClientRuntime.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.51\lib\net462\Microsoft.Rest.ClientRuntime.dll ..\packages\Microsoft.Web.Xdt.3.1.0\lib\net40\Microsoft.Web.XmlTransform.dll @@ -77,16 +77,16 @@ ..\packages\Microsoft.CrmSdk.Workflow.9.0.2.26\lib\net462\Microsoft.Xrm.Sdk.Workflow.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.Connector.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.CoreAssembly.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.Connector.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.CrmConnectControl.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.CrmConnectControl.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.Ui.Styles.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.Ui.Styles.dll - ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.49\lib\net462\Microsoft.Xrm.Tooling.WebResourceUtility.dll + ..\packages\Microsoft.CrmSdk.XrmTooling.WpfControls.9.1.0.51\lib\net462\Microsoft.Xrm.Tooling.WebResourceUtility.dll ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll @@ -182,6 +182,7 @@ + diff --git a/FetchXmlBuilder/packages.config b/FetchXmlBuilder/packages.config index 1abb61c4..1488e801 100644 --- a/FetchXmlBuilder/packages.config +++ b/FetchXmlBuilder/packages.config @@ -7,8 +7,8 @@ - - + + diff --git a/README.md b/README.md index 3ed86d2f..e7cad985 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![FXB](https://fetchxmlbuilder.com/FXB150) FetchXML Builder for XrmToolBox +# ![FXB](https://fetchxmlbuilder.com/origdocs/fxb150) FetchXML Builder for XrmToolBox [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=rappen/FetchXMLBuilder)](https://dependabot.com) diff --git a/XmlEditorUtils/packages.config b/XmlEditorUtils/packages.config index a771037b..87d2ed54 100644 --- a/XmlEditorUtils/packages.config +++ b/XmlEditorUtils/packages.config @@ -3,7 +3,7 @@ - +