How to separate CustomUI tags in Excel-DNA from OnAction methods? - excel-dna

In my .dna file I have:
<DnaLibrary Name="First Add-In" RuntimeVersion="v4.0" Language="C#">
<ExternalLibrary Path="MyLibrary.dll" Pack="true"/>
<Image Name="M" Path="M.png" Pack="true" />
<CustomUI>
<customUI xmlns='http://schemas.microsoft.com/office/2009/07/customui' loadImage='LoadImage'>
<ribbon>
<tabs>
<tab id='CustomTab' label='My 2010 Tab'>
<group id='SampleGroup' label='My Sample Group'>
<button id='Button1' label='My Second Button' image='M' size='normal' onAction='RunTagMacro' tag='ReformatSelection='/>
</group >
</tab>
</tabs>
</ribbon>
</customUI>
</CustomUI>
</DnaLibrary>
In my .cs file I have:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ExcelDna.Integration;
using System.Runtime.InteropServices;
using ExcelDna.Integration.CustomUI;
using System.Windows.Forms;
namespace MyLibrary
{
[ComVisible(true)]
public class Class1 : ExcelRibbon
{
public void ReformatSelection(IRibbonControl control)
{
MessageBox.Show("Hello");
}
}
}
When I load the addin the button and tab appears fine in the Ribbon, but clicking the button does not run the ReformatSelection method. In the example files provided with Excel-DNA all the subs and functions that are hooked to the onAction events are in the .dna file. I am trying to move them out of the .dna file and into the .cs file. What am I doing wrong?

Your signature for ReformatSelection() is not right for the onAction handler of a ribbon button.
It should be:
public void ReformatSelection(IRibbonControl control) {...}
You can get a list of all the Office Ribbon callbacks signatures here: http://msdn.microsoft.com/en-us/library/aa722523(v=office.12).aspx

Related

How to Publish to Folder while including Assembly Version in Path in Visual Studio 2022?

I'm building a .Net 6 WPF application in Visual Studio 2022 and I'm publishing to a folder.
I'd like the assembly version included in the folder path e.g. C:\Code\Publish\MyApplication_1.2.0.28\
My Google Fu is failing me and I can't find a solution.
I'm using a Text Template to generate the Assembly Version from a manually set <major>.<minor>.<patch>. and an autoincrementing <revision>. See below. This works exceptionally well. Thanks to https://makolyte.com/auto-increment-build-numbers-in-visual-studio/ including comments.
Following this StackOverflow I tried creating and calling a static method getting the assembly version from the publishprofile. I added the method to the .tt just to keep it together. The static method works if I just call it from MainWindow, but when editing the FolderProfile.pubxml as text and adding the call to the PublishDir: <PublishDir>C:\Code\Publish\MyApplication\$([VersionGetter]::GetVersion())</PublishDir>
and hitting the Publish-button, it opens the wizard to create a new publishprofile, so that is not correct syntax. From the linked documentation, it looks like I can call certain types from here. It works creating a GUID string with the following, so I guess that's just not the way to go: <PublishDir>C:\Code\Publish\MyApplication\$([System.Guid]::NewGuid())</PublishDir>
I actually stumpled upon this StackOverflow answer, but I'm unsure of how to implement it. It creates a directory, but how does the publish profile know about this directory? See code from that answer at the buttom.
So, StackOverflow, please help me :)
FolderProfile.pubxml
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>C:\Code\Publish\MyApplication\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net6.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>
Added this to .csproj file
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />
<!-- Automatic versioning end -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
<TransformOnBuild>true</TransformOnBuild>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>
Text Template: VersionAutoIncrement.tt
<## template debug="false" hostspecific="true" language="C#" #>
<## output extension=".cs" #>
<## assembly name="System.Core" #>
<## import namespace="System.IO" #>
<## import namespace="System.Linq" #>
<#
try
{
string currentDirectory = Path.GetDirectoryName(this.Host.TemplateFile);
string fullPath = Path.Combine(currentDirectory, "VersionAutoIncrement.cs");
string currentRevisionNumber = File.ReadLines(fullPath).First().Replace("//", "");
Revision = Convert.ToUInt16(currentRevisionNumber);
Revision++;
}
catch( Exception )
{
//Throws the first time since the output file doesn’t exist yet
}
#>
//<#= this.Revision #>
using System.Reflection;
[assembly: AssemblyVersion("1.2.0.<#= this.Revision #>")]
namespace FlexPOS_Importer
{
public static class VersionGetter
{
public static string GetVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
}
<#+
UInt16 Revision = 1;
#>
The generated VersionAutoIncrement.cs file
//29
using System.Reflection;
[assembly: AssemblyVersion("1.2.0.29")]
namespace FlexPOS_Importer
{
public static class VersionGetter
{
public static string GetVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version.ToString();
}
}
}
Section copied from this StackOverflow answer
As follows[Altered version of the code as described in the given
links]:
<PropertyGroup>
<AssemblyList>myfolder\myLibrary.dll</AssemblyList>
</PropertyGroup>
<Target Name="AssemblyInformations">
<GetAssemblyIdentity AssemblyFiles="$(AssemblyList)">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfos"/>
</GetAssemblyIdentity>
</Target>
<Message Text="Files: %(AssemblyInfos.Version)"/>
And then I would use something like the following to create a directory in the place you
want to publish:
<CreateProperty Value="$(Public_Shared_Folder)$(ProjectName)\">
<Output TaskParameter="Value" PropertyName="PublicFolderToDropZip" />
</CreateProperty>
<MakeDir Directories="$(PublicFolderToDropZip)"
Condition="$(Configuration)=='Release' AND Exists('$(PublicFolderToDropZip)')" />
The entire operation can be completely automated.

Xamarin.Forms.Xaml.XamlParseException exception in Xamarineforms

I am new to Xamarine.
I had created new blank Xamarine project in Visual studio 2019.
I am using this link to create demo native views in Xamarine forms : https://learn.microsoft.com/en-us/xamarin/xamarin-forms/platform/native-views/xaml
MainPage.Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidLocal="clr-namespace:App1.Android;assembly=App1.Android;targetPlatform=Android"
mc:Ignorable="d"
x:Class="App1.MainPage">
<StackLayout>
<!-- Place new controls here -->
<ios:UILabel Text="Hello World" TextColor="{x:Static ios:UIColor.Red}" View.HorizontalOptions="Start" />
<androidWidget:TextView Text="Hello World" x:Arguments="{x:Static androidLocal:MainActivity.Instance}" />
</StackLayout>
</ContentPage>
I had added skip Xamlcomplitation in MainPage.xaml.cs file.
[XamlCompilation(XamlCompilationOptions.Skip)]
Added in MainActivity.cs in App1.Android file.
internal static MainActivity Instance { get; private set; } // define Instance
Bit it shows error for
( 8976): Assembly Ref addref System.Xml[0xd1e768c0] -> System[0xe4b32b00]: 5
**Xamarin.Forms.Xaml.XamlParseException:** 'Position 15:52. Type MainActivity not found in xmlns clr-namespace:App1.Android;assembly=App1.Android;targetPlatform=Android'
[XamlCompilation(XamlCompilationOptions.Skip)]
Is something that you add to the Xaml backend code. Adding it to something else like in your case is likely to cause problems.

How to use C# Web API in Dynamics 365

I am using online instance of Dynamics 365. I have created C# Web API to export data from activity entity. When I am running it from visual studio it's working fine.
Now I want to call it on button click in Dynamics 365. The requirement is that when user click on the button then the Web API should be call and data will be exported.
I don't have idea, how to get this task done. can anyone help me to solve this problem. kindly provide me steps to get this task.
Web API code is given below
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.ServiceModel;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk;
using System.Windows.Forms;
namespace Experiments
{
class Program
{
private static OrganizationService _orgService;
static void Main(string[] args)
{
try
{
CrmConnection connection = CrmConnection.Parse(ConfigurationManager.ConnectionStrings["CrmOnline"].ConnectionString);
using (_orgService = new OrganizationService(connection))
{
var exportToExcelRequest = new OrganizationRequest("ExportToExcel");
exportToExcelRequest.Parameters = new ParameterCollection();
//Has to be a savedquery aka "System View" or userquery aka "Saved View"
//The view has to exist, otherwise will error out
exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("View", new EntityReference("savedquery", new Guid("{00000000-0000-0000-00AA-000010001902}"))));
exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("FetchXml", #"<?xml version='1.0'?>
<fetch distinct='false' mapping='logical' output-format='xml-platform' version='1.0'>
<entity name='activitypointer'>
<attribute name='subject'/>
<attribute name='ownerid'/>
<attribute name='prioritycode'/>
<attribute name='regardingobjectid'/>
<attribute name='activitytypecode'/>
<attribute name='statecode'/>
<attribute name='scheduledstart'/>
<attribute name='scheduledend'/>
<attribute name='activityid'/>
<attribute name='instancetypecode'/>
<attribute name='community'/>
<attribute name='senton'/>
<attribute name='statuscode'/>
<order descending='false' attribute='scheduledend'/>
<filter type='and'>
<condition attribute='actualdurationminutes' value='43800' operator='le'/>
</filter>
<link-entity name='systemuser' alias='activitypointerowningusersystemusersystemuserid' link-type='outer' visible='false' to='owninguser' from='systemuserid'>
<attribute name='internalemailaddress'/>
</link-entity>
<link-entity name='email' alias='email_engagement' link-type='outer' visible='false' to='activityid' from='activityid'><attribute name='isemailfollowed'/>
<attribute name='lastopenedtime'/>
<attribute name='delayedemailsendtime'/>
</link-entity>
</entity>
</fetch>"));
exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("LayoutXml", #"
<grid name='resultset' object='2' jump='fullname' select='1' icon='1' preview='1'>
<row name='result' id='activitypointerid'>
<cell name='activitytypecode' width='150' />
<cell name='statecode' width='112' />
<cell name='scheduledstart' width='110' />
<cell name='scheduledend' width='110' />
</row>
</grid>"));
//need these params to keep org service happy
exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("QueryApi", ""));
exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("QueryParameters",new InputArgumentCollection()));
var exportToExcelResponse = _orgService.Execute(exportToExcelRequest);
if (exportToExcelResponse.Results.Any())
{
File.WriteAllBytes("Activities.xlsx", exportToExcelResponse.Results["ExcelFile"] as byte[]);
}
}
}
catch (FaultException<OrganizationServiceFault> ex)
{
string message = ex.Message;
throw;
}
}
}
}
Thanks.
I'd recommend you compile your C# code as an Action and include it as a step in a workflow. See here on how to invoke a custom action from a workflow.
I'd then recommend you install the Ribbon Workbench. This will let you customise your forms and navigations to add one or many buttons. You can use the workbench to customise these buttons, setting their commands to call your workflow which in turn calls your Action (your C# code).
Note there are several solutions to achieve what you're asking, I've just suggested one. Other solutions would likely include JavaScript and calling the Web API client-side.
Since you are using Dynamics 365 online, I will recommend you to create a Microsoft FLOW App with a CDS connector. Hope this helps.

How to access backstage checkbox value in an Office addin?

I have a boolean property Settings.Default.MarkAsRead in the Setting.settings file, which I can access in my Ribbon class. What I'd like to do is set the value of a check box in my backstage section depending on the value of this property. Also if the user modifies it, I'll need to save the new value.
Any way I can do this?
This is my (simplified) xml:
<?xml version="1.0" encoding="UTF-8"?>
<customUI onLoad="Ribbon_Load"
xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<backstage>
<tab id="MyBackstageSection" label="MyBackstageSection"
columnWidthPercent="30" insertAfterMso="TabInfo" visible="true" >
<firstColumn>
<group id="grpOne" label="Configuration">
<bottomItems>
<checkBox id="markAsRead" label="Mark as read"
getPressed="markAsRead_GetPressed" />
<button id="save" label="Save Preferences" onAction="save_Click"/>
</bottomItems>
</group>
</firstColumn>
</tab>
</backstage>
</customUI>
I didn't find a way to access the xml elements from the Ribbon_Load method, so I've created a boolean property in the ribbon class that I update using the GetPressed and OnAction callbacks:
xml:
<checkBox id="markAsRead" label="Mark as read"
onAction="markAsRead_OnAction" getPressed="markAsRead_GetPressed"/>
c#:
private bool MarkAsRead { get; set; }
public bool markAsRead_GetPressed(Office.IRibbonControl control)
{
this.MarkAsRead = Settings.Default.MarkAsRead;
return this.MarkAsRead;
}
public void markAsRead_OnAction(Office.IRibbonControl control, bool isPressed)
{
this.MarkAsRead = isPressed;
}

How to consume wcf service running as windows service in ajax client

I have created a WCF service and it is hosted in windows service. When I have added a web reference to that service to an asp.net web forms project through right client menu in the solution explorer I am able to access the service and add reference to it.
Now I want to access this service through AJAX client (i.e in ASP.NET project through ScriptManager component)and call the service in a timer to get continuous stream of values.
I have never worked on AJAX or web that much, I did not find an suitable example on net on this.
I'm using WSHttpBinding.
I'm posting my code so that you can tell where I'm doing wrong.
WCF Service Library Code:
ITestService.cs code....
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Web;
namespace TestServiceLibrary
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract(Namespace="TestServiceLibrary")]
public interface ITestService
{
[OperationContract]
[WebGet]
double Add(double n1, double n2);
// TODO: Add your service operations here
}
}
TestService.cs code...............
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace TestServiceLibrary
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
public class TestService : ITestService
{
public double Add(double n1, double n2)
{
return n1 + n2;
}
}
}
TestServiceHost.cs (code of console application)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using TestServiceLibrary;
namespace TestServiceHost
{
class Program
{
static void Main(string[] args)
{
ServiceHost myhost = new ServiceHost(typeof(TestService));
myhost.Open();
while (System.Console.ReadKey().Key != System.ConsoleKey.Enter)
{
//System.Threading.Thread.Sleep(100);
}
myhost.Close();
}
}
}
XML Configuration of app.config... same in both wcf service library and wcf service host(console application in this case..)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service name="TestServiceLibrary.TestService" behaviorConfiguration="TestServiceLibrary.Service1Behavior">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8731/TestServiceLibrary/TestService/" />
</baseAddresses>
</host>
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint name="TestService_wsHttpBinding" address ="" binding="wsHttpBinding" contract="TestServiceLibrary.ITestService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="TestServiceLibrary.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Web Client (asp.net client, default.aspx) code...
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Simple AJAX Service Client Page</title>
<script type="text/javascript">
// <![CDATA[
// This function creates an asynchronous call to the service
function makeCall(operation){
var n1 = document.getElementById("num1").value;
var n2 = document.getElementById("num2").value;
// If user filled out these fields, call the service
if(n1 && n2){
// Instantiate a service proxy
var proxy = new TestServiceLibrary.ITestService();
// Call correct operation on proxy
switch(operation){
case "Add":
proxy.Add(parseFloat(n1), parseFloat(n2), onSuccess, onFail, null);
break;
}
}
}
// This function is called when the result from the service call is received
function onSuccess(mathResult){
document.getElementById("result").value = mathResult;
}
// This function is called if the service call fails
function onFail(){
document.getElementById("result").value = "Error";
}
// ]]>
</script>
</head>
<body>
<h1>
Simple AJAX Service Client Page</h1>
<p>
First Number:
<input type="text" id="num1" /></p>
<p>
Second Number:
<input type="text" id="num2" /></p>
<input id="btnAdd" type="button" onclick="return makeCall('Add');" value="Add" />
<p>
Result:
<input type="text" id="result" /></p>
<form id="mathForm" action="" runat="server">
<asp:ScriptManager ID="ScriptManager" runat="server">
<Services>
<asp:ServiceReference Path="http://localhost:8732/TestServiceLibrary/TestService/" />
</Services>
</asp:ScriptManager>
</form>
</body>
</html>
The error Im getting when accessing the webservice through asp.net in ajax is Microsoft JScript runtime error: 'TestServiceLibrary' is undefined
Please go through this code and help me in finding the problem. Thank you all for your responses.
Looks like the problem is with my service hosting and the endpoint i'm using.
I should modified my service hosting in the console application to use WebServiceHost instead of ServiceHost, then only the ajax clients can talk to my service. Instead of wsHttpBinding, I should use webHttpBinding.
So the code for webHosting is as follows.
using (var host = new WebServiceHost(
typeof(TestService)))
{
// Start listening for messages
host.Open();
Console.WriteLine("Press any key to stop the service.");
Console.ReadKey();
// Close the service
host.Close();
}
The xml configuration of my console is
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<services>
<service
name="TestServiceLibrary.TestService"
behaviorConfiguration="">
<endpoint address="http://localhost:8732/TestService"
binding="webHttpBinding"
bindingConfiguration=""
name="TestService_WebHttp"
contract="TestServiceLibrary.ITestService" />
</service>
</services>
</system.serviceModel>
</configuration>
Now when I did this changes I'm able to call my service through ie using the following url in ie http://localhost:8732/TestService/Add?n1=20&n2=20 and result returned by it is as follows <double xmlns="http://schemas.microsoft.com/2003/10/Serialization/">40</double>
Finally i found the solution to my problem. Im using JSON as way of communicating data and the script for receiving the data is as follows:
<script type="text/javascript">
$("#mybutton").click(function () {
$.getJSON("http://localhost:8732/TestService/Add", null, function (result) {
});
});
</script>
Use some tool like firebug to determine what is happening with the request. WSHttpBinding is secure by default. Check your security settings. Try first with no security to make sure it is not a security issue.
Have you attempted to connect to the service from the AJAX client yet? If so, are you getting any errors?
Without seeing code, there could be a number of things as Chandermani has said.
I've not done AJAX with WCF, but looking over the article Preet recommended, I would suggest checking (if you haven't already) that your AJAX client has the necessary code as per the article.
Are your service operations decorated with [WebGet]?
Do you have the config file for the AJAX client set up properly? Is the service's config file set up properly?

Resources