I have built a VSTO addin for Excel which refreshes a number of PowerQuery workbook connections. So as to avoid an error blocking the main thread causing a "Cartridge not loaded" error I have to run the main code in another thread.
I am doing this via Async method.
I also need this to work from the command line so i have exposed the code as a COM visible interface and exposed it in ThisAddIn.vb
Protected Overrides Function RequestComAddInAutomationService() As Object
If headless Is Nothing Then
headless = New HeadlessExec()
End If
Return headless
End Function
This is the interface class
Imports System.Data
Imports System.Runtime.InteropServices
Imports log4net
Imports System.Threading.Tasks
<ComVisible(True)>
Public Interface IHeadlessExec
Function RefreshDIT() As Task(Of Boolean)
Function GetState() As String
Function GetStatusDetails() As String
End Interface
<ComVisible(True)>
<ClassInterface(ClassInterfaceType.None)>
Public Class HeadlessExec
Implements IHeadlessExec
Private log As ILog
Private logdir As String = ThisAddIn.logdir
Sub New()
'Initialise here
log = LogManager.GetLogger("HeadlessExec")
log.Info("Constructor")
End Sub
Public Async Function RefreshDIT() As Task(Of Boolean) Implements IHeadlessExec.RefreshDIT
log.Debug("Start")
Dim pq As New PowerQueryRefresh
Dim ExecDIT As Task(Of Boolean) = pq.ExecRefreshInNewThread()
Dim status As Boolean = Await ExecDIT
Return status
log.Debug("End")
End Function
Public Function GetState() As String Implements IHeadlessExec.GetState
log.Debug("Start")
Dim pq As New PowerQueryRefresh
GetState = pq.GetState
log.Debug("GetStateVSTO:" & GetState)
log.Debug("End")
End Function
Public Function GetStatusDetails() As String Implements IHeadlessExec.GetStatusDetails
log.Debug("Start")
Dim pq As New PowerQueryRefresh
GetStatusDetails = pq.GetStatusDetails
log.Debug("GetStatusDetailsVSTO:" & GetStatusDetails)
log.Debug("End")
End Function
I am calling this from Powershell via COM as follows - the key part is ExecuteVSTOAdd_DITRefresh
:-
Function RunVSTOProc() {
$error.Clear()
try {
$FilePath = GetMostRecentFile($BASEDIR)
OpenExcelWithFile($FilePath)
$ret = ExecuteVSTOAdd_DITRefresh
} catch {
HandleError($_)
}
if ($vstostate -eq "Error"){
CleanUpExcel
Exit
}
if (!$error){
# Only save it if we have no errrors
$newname = NewName($FilePath)
Write-Host "Saving as $newname"
$workbook.saveAs($newname)
}
CleanUpExcel
Write-Host "Completed Running DIT"
}
ExecuteVSTOAdd_DITRefresh
Function ExecuteVSTOAdd_DITRefresh(){
try {
$DITAddin = $global:excel.COMAddins.Item("DITUtility")
Write-Host "Addin $($DITAddin.ProgID) is connected"
$autom = $DITAddin.Object
$CallProc = $autom.RefreshDIT()
Write-Host "DIT Refreshed within VSTO"
$CallProc
} Catch {
HandleError($_)
}
}
This issue is that when RefreshDIT runs Powershell doesn't wait for it to complete. EDIT :- I had an issue with establishing com automation - NOW - i can see details for $DITAddin and I can see the exposed methods BUT I cannot see the exposed method RefreshDIT - even though i can call it - this one is Async and the others are not Async method. Its also not obvious to me how to call it Async from Powershell so it functions as an Async method. Any pointers?
$DITAddin | Get-Member
TypeName: System.__ComObject#{000c033a-0000-0000-c000-000000000046}
Name MemberType Definition
---- ---------- ----------
Application Property IDispatch Application () {get}
Connect Property bool Connect () {get} {set}
Creator Property int Creator () {get}
Description Property string Description () {get} {set}
Guid Property string Guid () {get}
Object Property IDispatch Object () {get} {set}
Parent Property IDispatch Parent () {get}
ProgId Property string ProgId () {get}
$autom | Get-Member
TypeName: System.__ComObject#{159faa2b-4a8e-3bca-bb69-e2268f06d436}
Name MemberType Definition
---- ---------- ----------
GetState Method string GetState ()
GetStatusDetails Method string GetStatusDetails ()
If I run
$CallProc = $autom.RefreshDIT()
$CallProc | Get-Member
TypeName: System.__ComObject
Name MemberType Definition
---- ---------- ----------
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
ToString Method string ToString
()
There is no Run() method and if I try and execute it i get
$CallProc.Run()
Method invocation failed because [System.__ComObject] does not contain a method named 'Run'.
At line:1 char:1
+ $CallProc.Run()
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
That failed with ERROR ExecuteVSTOAdd_DITRefresh :
RunDIT_VSTO.ps1:164 char:9
+ [System.Threading.Tasks.Task]$tskRefreshDIT = $autom.RefreshD ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
Solved the problem.
The Async method wasn't displaying in Powershell when performing a Get-Member on the object but the NON Async methods were.
I already had a an async function with an Await statement in VB.NET so i wrapped it a function without the Async modifier and called that:-
This in the main body of the code:-
Public Async Function ExecRefreshInNewThread() As Task(Of Boolean)
Dim msg As String
Try
Dim tasks As New List(Of Tasks.Task)()
tasks.Add(Task.Run(AddressOf RefreshSequenceOfConnectionsH))
Await Task.WhenAll(tasks)
log.Info("Executed without error")
Return True
Catch e As Exception
msg = FormatExceptionMsg(e)
log.Error(msg)
Return False
End Try
End Function
Public Function ExecRefreshInNewThread_v2() As Boolean
Dim boo As Task(Of Boolean) = ExecRefreshInNewThread()
Return boo.Result
End Function
This in the interface class:-
Public Function RefreshDITv2() As Boolean Implements IHeadlessExec.RefreshDITv2
log.Debug("Start")
Dim pq As New PowerQueryRefresh
Dim ExecDIT As Boolean = pq.ExecRefreshInNewThread_v2
Return ExecDIT
log.Debug("End")
End Function
Then this worked in Powershell:-
Function ExecuteVSTOAdd_DITRefresh(){
try {
$DITAddin = $global:excel.COMAddins.Item("DITUtility")
Write-Host "Addin $($DITAddin.ProgID) is connected"
$autom = $DITAddin.Object
$tskRefreshDIT = $autom.RefreshDITv2()
Write-Host "DIT Refreshed within VSTO $CallProc"
$tskRefreshDIT
} Catch {
HandleError($_)
}
}
Now it waits before moving on.
Try using the Wait method of the task from your async RefreshDIT function:
$tskRefreshDIT = $autom.RefreshDIT()
$bolSuccess = $tskRefreshDIT.Run()
$bolSuccess = $task.Wait(60000)
if ($bolSuccess -eq $true) {
$CallProc
}
Related
I have a script where one of the closure delegates with another class object.
Now this closure should take no parameters or multiple parameters and inside code it should access passed delegated object methods
suppose
class Foo {
public boolean verifyName(String name);
public boolean verifyNameAndType(String name, PersonType type);
public boolean verifyNameAndTypeAndAge(String name, PersonType type, int age);
}
class Bar {
def getNames = { String name ->
if (verifyName(name)){
// do something
}
};
def getNames = { String name, PersonType personType ->
if (verifyNameAndType(name, personType)) {
// do something
}
}
def getNames = { String name, PersonType personType, int age ->
if (verifyNameAndTypeAndAge(name, personType, age)) {
// do something
}
}
Foo foo = new Foo()
Bar bar = new Bar();
bar.delegate = foo;
Closure NAME = bar.getNames;
NAME closure is available to user where he types
NAME('shakira');
NAME('affan', DOCTOR);
NAME('siddique', TEACHER, '45455555');
base on parameter it should call specific closure but it seems multiple closure with same name is not allowed.
also user can provide null values inside parameters but still it will call specific closure even if it is null
e.g NAME('shakira', null) should call exact closure getNames(name, personType)
I have already checked this url but my logic will not cover my logical part where User provides null parameters inside method
Groovy Closure with parameters
you can't define several variables with the same name in one class,
however you can define several methods with one name but with different parameters
so your code could look like this:
class Foo {
public boolean verifyName(String name){println 'verifyName'}
public boolean verifyNameAndType(String name, String type){println 'verifyNameAndType'}
public boolean verifyNameAndTypeAndAge(String name, String type, int age){println 'verifyNameAndTypeAndAge'}
}
class Bar {
#Delegate Foo delegate
def getNames ( String name ){
println '1'
verifyName(name)
}
def getNames (String name, String personType ){
println 2
verifyNameAndType(name,personType)
}
def getNames (String name, String personType, int age ){
println 3
verifyNameAndTypeAndAge(name,personType,age)
}
}
Foo foo = new Foo()
Bar bar = new Bar(delegate:foo);
Closure NAME = bar.&getNames; // &methodName returns a method closure
//NAME closure is available to user where he types
NAME('shakira');
NAME('affan', 'DOCTOR');
NAME('siddique', 'TEACHER', 45455555);
How would I access the string filename from a different method?
protected void AjaxFileUpload1_UploadComplete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
{
filename = Server.MapPath("images/") + e.FileName.ToString();
AjaxFileUpload1.SaveAs(filename);
}
I have tried declaring filename in the scope of the class like this:
private string filename = String.Empty;
But when trying to access from another method like this:
public void GetFilename()
{
lblResults.Text = filename;
}
The string is empty. What am I missing?
It depends from the order you call your methods, if your call GetFilename() before AjaxFileUpload1_UploadComplete(...) has been called at least once. Your filename member will be empty, because you initialise it empty.
As you are dealing with asynchronous, you should make sure to wait for filename to be set by your function.
I understand calling a base method with arguments will default to the method without any default parameters. Great! Fully understandable!
Resolving Ambiguities with Optional Parameters and Named Arguments
However, consider this scenario
using System;
class Class1 {
public virtual string method1( string test ) {
return test;
}
}
class Class2 : Class1 {
// Method is hidden!
public override string method1( string test ) {
return test;
}
public virtual string method1( string test, string test2 = " - this shouldn't be called" ) {
return test + test2;
}
}
class Class3 : Class2 {
static int Main( string[] args ) {
var c = new Class3();
string theString;
theString = c.test1();
System.Console.WriteLine( theString ); // "Test - this shouldn't be called"
theString = c.test2( );
System.Console.WriteLine( theString ); // "Test"
return 0;
}
public string test1() {
return base.method1( "test" ); // calls base.method1( string, string )
}
public string test2() {
return ( ( Func<string, string> )base.method1)("test"); // calls base.method1( string )
}
}
Clearly the intended behavior of calling the base method should only print out "Test" but it does not and instead calls the method with default parameters. I'm still new to IL but it also shows that it clearly resolves to the method with default parameters.
This essentially hides the method signature method( string ) in child classes.
What exactly is happening here?
Is this a bug in C#? A bug in the compiler?
Are method calls higher up in the generation chain SUPPOSED to have precedence
over overwritten calls of their child?
Is there something I am just missing?
For the curious, the problem was resolved by using a delegate function
( ( Func<string, string> )base.method)("Test"); // prints out "Test"
**edit - The code now compiles if copy pasted
I am new to servicestack.net, and I am struggling to access the header information inside my methods. I am attaching the code I am using. It is in vb.net
Service stack Classes
a)Service Stack Request Class
Public Class LeaveManagementDashboardRequest
Public Property ClientID As String
Public Property DateFormatID As String
Public Property UserID As String
Public Property NOOFROWS As String
End Class
b)Service Stack Response Class
Public Class LeaveManagementDashboardResponse
Public Property data As String
End Class
c)Service Stack Service Class(Actual Service implementation)
Public Class LeaveManagementDashboardService
Implements IService(Of LeaveManagementDashboardRequest)
Private sqlCon As New SqlConnection
Public Function Execute(ByVal request As LeaveManagementDashboardRequest) As Object Implements ServiceStack.ServiceHost.IService(Of LeaveManagementDashboardRequest).Execute
Dim ds As New DataSet
If sqlcon.State = ConnectionState.Closed Then
Common.OpenConnection(sqlCon)
End If
Dim ClientID As String = request.ClientID
Dim UserID As String = request.UserID
Dim DataFormatID As String = request.DateFormatID
Dim NOOFROWS As String = request.NOOFROWS
Dim sqlcmd As New SqlCommand("abcdefg", sqlcon)
sqlcmd.CommandType = CommandType.StoredProcedure
sqlcmd.Parameters.Add(New SqlParameter("#UserID", SqlDbType.Int, 0))
sqlcmd.Parameters.Add(New SqlParameter("#DateFormatID", SqlDbType.TinyInt, 0))
sqlcmd.Parameters.Add(New SqlParameter("#Count", SqlDbType.SmallInt, 0))
sqlcmd.Parameters.Add(New SqlParameter("#ClientID", SqlDbType.Int, 0))
sqlcmd.Parameters(0).Value = UserID
sqlcmd.Parameters(1).Value = DataFormatID
sqlcmd.Parameters(2).Value = NOOFROWS
sqlcmd.Parameters(3).Value = ClientID
Dim dsResult As New DataSet
Dim sqlda As New SqlDataAdapter(sqlcmd)
sqlda.Fill(dsResult)
Dim obj As String = Common.GetJson(dsResult.Tables(0))
' obj.countries = lstCountries
sqlcon.Close()
sqlcon.Dispose()
Return New LeaveManagementDashboardResponse With {.data = obj}
Return dsResult
End Function
End Class
Routes are defined in the Global.aspx.cs file as given below
Public Class _Global
Inherits System.Web.HttpApplication
Public Class HelloAppHost
Inherits AppHostBase
Public Sub New()
MyBase.New("Plant Best Services", GetType(HelloAppHost).Assembly)
End Sub
Public Overrides Sub Configure(ByVal container As Container)
Routes.Add(Of LeaveManagementDashboardRequest)("/pml/Dashboard/LeavesRequests")
End Sub
End Class
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
Dim apphost = New HelloAppHost()
apphost.Init()
End Sub
It's hard to decipher what question is being asked here, I'm going to assume you want to know how to access the header information in your services.
You should first look at using ServiceStack's New API for new services. When you inherit from Service you can access the HTTP Headers with:
public class MyService : Service
{
public LeaveManagementDashboardResponse Any(LeaveManagementDashboard request)
{
var httpHeader = base.Request.Headers["headerName"];
}
}
If you want to continue to use the Old API (e.g. IService<T>) then you want to implement the IRequiresRequestContext interface to get ServiceStack to inject the RequestContext into your service. Read the wiki docs for more info on this.
I have the following model:
Public Class MyModel
Public Property MyModelId As Integer
Public Property Description As String
Public Property AnotherProperty As String
End Class
Is there a method to get a property name of the Model as a string representation like the following code?
Dim propertyName as String = GetPropertyNameAsStringMethod(MyModel.Description)
So the propertyName variable has "Description" as value.
Check the Darin Dimitrov' answer on this SO thread - Reflection - get property name.
class Foo
{
public string Bar { get; set; }
}
class Program
{
static void Main()
{
var result = Get<Foo, string>(x => x.Bar);
Console.WriteLine(result);
}
static string Get<T, TResult>(Expression<Func<T, TResult>> expression)
{
var me = expression.Body as MemberExpression;
if (me != null)
{
return me.Member.Name;
}
return null;
}
}
Hope this help..
Here is a helper extension method you can use for any property:
public static class ReflectionExtensions
{
public static string PropertyName<T>(this T owner,
Expression<Func<T, object>> expression) where T : class
{
if (owner == null) throw new ArgumentNullException("owner");
var memberExpression = (MemberExpression)expression.Body;
return memberExpression.Member.Name;
}
}
However, this will only work on instances of a class. You can write a similar extension method that will operate directly on the type instead.
You need to do it using reflection.
There are already loads of posts on stack overflow like this:
How to get current property name via reflection?
Reflection - get property name
Get string name of property using reflection
Reflection - get property name
I believe that the answer will be along the lines of:
string prop = "name";
PropertyInfo pi = myObject.GetType().GetProperty(prop);
Create an extension method and then use it where needed.
Private Shared Function GetPropertyName(Of T)(exp As Expression(Of Func(Of T))) As String
Return (DirectCast(exp.Body, MemberExpression).Member).Name
End Function
have a look at this post as well.
I have solved this issue editing a bit #NiranjanKala's source example,
converting the code in vb.Net like this
<System.Runtime.CompilerServices.Extension()> _
Public Function GetPropertyName(Of T, TResult)(expression As Expression(Of Func(Of T, TResult))) As String
Dim [me] = TryCast(expression.Body, MemberExpression)
If [me] IsNot Nothing Then
Return [me].Member.Name
End If
Return Nothing
End Function
Then I am able to call the extension like this
Dim propertyName as String = GetPropertyName(Of MyModel, String)(Function(x) x.Description)
Then propertyName variable has "Description" as string value.