Add and populate an IEnumerable property to an API call - c#

I'm using an older 3rd party API to connect to a legacy system.
Here is the code:
AutoMaximizer.AuthUser yourCredentials = new AutoMaximizer.AuthUser
{
UserKey = "x1213"
};
AutoMaximizer.Pagers availableCars = new AutoMaximizer.Pagers
{
TotalCalls = 65,
Router = 91220
};
ISessionManagement s = new ManagementClient();
FactoryResponse response;
response = await s.GetAll(yourCredentials, new ListRequest
{
Pagers = availableCars,
SortIncreasing = true
});
It's working, but I want to add another property when I make the request.
The property is called Types and is a IEnumerable<Type>. The API docs state:
Types = Gets or sets an enumeration of types to include in the response.
And in the API reference, I found it:
public enum Type : int
{
[System.Runtime.Serialization.EnumMemberAttribute()]
Auto = 0,
[System.Runtime.Serialization.EnumMemberAttribute()]
Truck = 1,
[System.Runtime.Serialization.EnumMemberAttribute()]
Motorcycle = 2
}
But I'm not quite sure how to add it to the GetAll method.
I tried adding this:
List<AutoMaximizer.Type> types = new List<AutoMaximizer.Type>();
types.Add(AutoMaximizer.Type.Auto);
types.Add(AutoMaximizer.Type.Truck);
types.Add(AutoMaximizer.Type.Motorcycle);
And then this:
response = await s.GetAll(yourCredentials, new ListRequest
{
Pagers = availableCars,
SortIncreasing = true,
Types = types
});
But that gives me this error:
Cannot implicitly convert type Systems.Collections.Generic.List<AutoMaximizer.Type> to AutoMaximizer.Type[]
I am not sure what to do now...
Is there a way to get this to work?
Thanks!

Read your error, it wants an array, not a list:
response = await s.GetAll(yourCredentials, new ListRequest
{
Pagers = availableCars,
SortIncreasing = true,
Types = new[] { Type.Auto, Type.Truck, Type.Motorcycle },
});

According to the error, the ListRequest is specifically looking for an array, not any generic collection. You can convert the list to an array:
response = await s.GetAll(yourCredentials, new ListRequest
{
Pagers = availableCars,
SortIncreasing = true,
Types = types.ToArray()
});
Or just use an array to begin with:
AutoMaximizer.Type[] types = new [] { AutoMaximizer.Type.Auto, AutoMaximizer.Type.Truck, AutoMaximizer.Type.Motorcycle };

Related

Make the request keeping the parameter string array

I add 2 fields on my update request and I still didnt found a way to pass these 2 fields. I try someting link StringArrayContent, or ArrayContent that does not exist. I also try MultipartContent. How can I sent my request keeping downloadableInternal and downloadableExternal as string array?
The values usually are empty [] or ["original", "pdf"]
return new ShowpadUpdateAssetRequest()
{
ExternalFileUri = externalFileUri,
externalDate = externalDate,
externalId = externalId,
file = file,
name = name,
isDivisionShared = isDivisionShared,
downloadableInternal = downloadableInternal,
downloadableExternal = downloadableExternal,
Url = url,
Bearer = bearer,
ResponseType = typeof(ShowpadUpdateAssetResponse),
Content = new MultipartFormDataContent()
{
{new StringContent(externalId), nameof(externalId)},
{new StringContent(name), nameof(name)},
{new StringContent(externalDate), nameof(externalDate)},
{new StringContent(isDivisionShared.ToString()), nameof(isDivisionShared) },
{new StringContent(downloadableInternal.ToString()) ,nameof(downloadableInternal) },
{new StringContent(downloadableExternal.ToString()) ,nameof(downloadableExternal) },
{new ByteArrayContent((await fileContent.ReadAsByteArrayAsync())), nameof(file), file}
}
};
This depends on how your service handles the request. Here is an example you can try. You can also try to remove the [] suffix, some service may regard repetitive query parameters as an array.
var c = new MultipartFormDataContent();
var key = nameof(downloadableInternal) + "[]";
foreach(string data in downloadableInternal)
c.Add(new StringContent(data), key);

Elasticsearch Nest unit tests with Moq : fail to mock concrete class

I need to mock some methods and properties in Nest.IElasticClient interface that is used in my repo.
As example I need to stub the Indices.Exists() method to return an ExistsResponse having an Exists property returning true.
The problem is that the concrete class has no interface implementation, nor setter on the Exists property, and is not declared virtual neither in Nest lib:
public class ExistsResponse : ResponseBase
{
public ExistsResponse();
public bool Exists { get; }
}
public ExistsResponse Exists(Indices index, Func<IndexExistsDescriptor, IIndexExistsRequest> selector = null);
So for the mocking I tried to set the property anyway on the concrete class, but it failed with all the following methods, I have no idea on how to do ...
/* Fail with exception :
System.NotSupportedException : Unsupported expression: x => x.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock1 = new Mock<ExistsResponse>();
obj.SetupGet(f => f.Exists).Returns(true);
/* Fail with exception :
System.NotSupportedException : Unsupported expression: f => f.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock2 = Mock.Of<ExistsResponse>(x => x.Exists == true);
/* Fail with exception :
System.ArgumentException : Property set method not found.
*/
var mock3 = new ExistsResponse();
var property = typeof(ExistsResponse).GetProperty("Exists", BindingFlags.Public | BindingFlags.Instance);
property.SetValue(mock3, true);
/* Fail with exception :
System.NullReferenceException (setter is null)
*/
var mock4 = new ExistsResponse();
var setter = property.GetSetMethod(true);
setter.Invoke(mock4, new object[] { true });
// My Mock on the Indices.Exists method
var elasticMock = new Mock<IElasticClient>();
elasticMock
.Setup(x => x.Indices.Exists(It.IsAny<string>(), null))
.Returns(/*** my stubbed object here ***/); // <== how to stub the concrete class to return a ExistsResponse.Exists = true ?
My libs :
Nest 7.12.1
Moq 4.15.2
XUnit 2.4.1
.Net 5
Thank you for your help
It is usually not good to mock what you don't own but if you want to mock elastic client responses the best approach will be to use InMemorryConnection, you can instantiate a new elasticClient with InMemorryConnection like this:
InMemorry Elastic Client:
var connection = new InMemoryConnection();
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection);
var client = new ElasticClient(settings);
Then you will need to fake the response and ask client to return it like below:
var response = new
{
took = 1,
timed_out = false,
_shards = new
{
total = 2,
successful = 2,
failed = 0
},
hits = new
{
total = new { value = 25 },
max_score = 1.0,
hits = Enumerable.Range(1, 25).Select(i => (object)new
{
_index = "project",
_type = "project",
_id = $"Project {i}",
_score = 1.0,
_source = new { name = $"Project {i}" }
}).ToArray()
}
};
var responseBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response));
var connection = new InMemoryConnection(responseBytes, 200);
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection).DefaultIndex("project");
var client = new ElasticClient(settings);
var searchResponse = client.Search<Project>(s => s.MatchAll());
You can find more information about InMemorryConnection here.
If you want to properly follow don't mock what you don't own you need to do following steps:
Wrap ElasticClient and all Request and Responses of ElasticClient in this library, then you can easily mock what you need.
You should test your wrapper class with Integration test to be sure your wrapped class work as expected.
UPDATE:
For mocking Indices.Exists you need only to send 200 OK so you only need to do this:
var connection = new InMemoryConnection(responseBytes, 200);

Cannot implicitly convert type 'F_M.Commitment_Ledger_Data__Public_Type' to 'F_M.Commitment_Ledger_Data__Public_Type[]'

I am trying to use the "Put_Ledger" function inside the Financial_management API in Workday, but I keep on getting an error when I try to add the object[] to the object (as it states in the API to do).
Workday has been no help in solving this issue. Here is a sample of the code. The objects are creates, and then added to parent objects:
Ledger_Only_DataType ldOnly = new Ledger_Only_DataType
{
Actuals_Ledger_ID = "1234567",
Can_View_Budget_Date = true
};
//Commitment_Ledger_data
Commitment_Ledger_Data__Public_Type cl = new Commitment_Ledger_Data__Public_Type
{
Commitment_Ledger_Reference = ledgerObject,
Enable_Commitment_Ledger = true,
Spend_Transaction_Data = st,
Payroll_Transaction_Data = pt
};
// This is where the error occurs:
ldOnly.Commitment_Ledger_Data = cl;
Error message:
"Cannot implicitly convert type 'CallWorkdayAPI.Financial_Management.Commitment_Ledger_Data__Public_Type' to 'CallWorkdayAPI.Financial_Management.Commitment_Ledger_Data__Public_Type[]"
Use lists and convert them to an array. It's easier:
List<Commitment_Ledger_Data__Public_Type> cls = new List<Commitment_Ledger_Data__Public_Type>();
Commitment_Ledger_Data__Public_Type cl1 = new
Commitment_Ledger_Data__Public_Type
{
Commitment_Ledger_Reference = ledgerObject,
Enable_Commitment_Ledger = true,
Spend_Transaction_Data = st,
Payroll_Transaction_Data = pt
};
cls.Add(cl1);
ldOnly.Commitment_Ledger_Data = cls.ToArray();
You can simplify and do it inside the initializer as well
Not familiar with Workday, but I am assuming
ldOnly.Commitment_Ledger_Data
Is an array of: Commitment_Ledger_Data__Public_Type
So you need to set it equal to an array of that type, whereas currently you are setting it equal to a single object of that type.
Ledger_Only_DataType ldOnly = new Ledger_Only_DataType
{
Actuals_Ledger_ID = "1234567",
Can_View_Budget_Date = true
};
//Commitment_Ledger_data
Commitment_Ledger_Data__Public_Type cl = new
Commitment_Ledger_Data__Public_Type
{
Commitment_Ledger_Reference = ledgerObject,
Enable_Commitment_Ledger = true,
Spend_Transaction_Data = st,
Payroll_Transaction_Data = pt
};
Commitment_Ledger_Data__Public_Type[] cls = new Commitment_Ledger_Data__Public_Type[1];
cls[0] = cl;
ldOnly.Commitment_Ledger_Data = cls;
The error message is telling you what the problem is - you're trying to assign a single instance of a Commitment_Ledger_Data__Public_Type type to an object that represents an array of that type (Commitment_Ledger_Data).
You should be able to do the assignment using an array (with the single item you created as it's only member) instead:
ldlOnly.Commitment_Ledger_Data = new[] {cl};
Or you could shorten the whole thing to use initializer syntax:
var ldOnly = new Ledger_Only_DataType
{
Actuals_Ledger_ID = "1234567",
Can_View_Budget_Date = true,
Commitment_Ledger_Data = new[]
{
new Commitment_Ledger_Data__Public_Type
{
Commitment_Ledger_Reference = ledgerObject,
Enable_Commitment_Ledger = true,
Spend_Transaction_Data = st,
Payroll_Transaction_Data = pt
}
}
};

How to add a SOAP parameter to a C# service reference

I have created service references in Visual Studio 2017 from WSDL's provided by our client. One of them requires an attribute/parameter like:
<Item ActionCode="02">
I'm new to SOAP services and can't figure out how to add the ActionCode. I see it in the object browser and in the References.cs.
Here is my code so far (which works for a similar call with no attribute):
BYDUpdateTimeSvc.EmployeeTimeCreateRequestMessage_sync req = new BYDUpdateTimeSvc.EmployeeTimeCreateRequestMessage_sync()
{
BasicMessageHeader = new BYDUpdateTimeSvc.BusinessDocumentBasicMessageHeader(),
EmployeeTime = new BYDUpdateTimeSvc.EmployeeTimeCreateRequest()
{
EmployeeTimeAgreementItemUUID = new BYDUpdateTimeSvc.UUID { Value = rec.employeeTimeAgreement },
Item = new BYDUpdateTimeSvc.EmployeeTimeCreateRequestItem[1]
{
new BYDUpdateTimeSvc.EmployeeTimeCreateRequestItem()
{
TypeCode = activityCode,
PaymentTypeCode = locationCode,
EmployeeTimeValidity = _dateValidity
}
}
}
};
How do I add that parameter/attribute?
I don't know anything about the API you are using.
That said, have you tried setting the property using object initializer syntax.
BYDUpdateTimeSvc.EmployeeTimeCreateRequestMessage_sync req = new BYDUpdateTimeSvc.EmployeeTimeCreateRequestMessage_sync()
{
BasicMessageHeader = new BYDUpdateTimeSvc.BusinessDocumentBasicMessageHeader(),
EmployeeTime = new BYDUpdateTimeSvc.EmployeeTimeCreateRequest()
{
EmployeeTimeAgreementItemUUID = new BYDUpdateTimeSvc.UUID { Value = rec.employeeTimeAgreement },
Item = new BYDUpdateTimeSvc.EmployeeTimeCreateRequestItem[1]
{
new BYDUpdateTimeSvc.EmployeeTimeCreateRequestItem()
{
TypeCode = activityCode,
PaymentTypeCode = locationCode,
EmployeeTimeValidity = _dateValidity
}, // added comma
ActionCode = "02"; // set action code here
}
}
};

ODataComplexValue Actions and Casting

So I'm new to OData and I'm using Simple.OData.Client
So my code first looked like this :
var context = new ODataClient("http://localhost:51861/API/");
var test = context.For<Account>()
.Key("00010017")
.Action("Testing")
.Set(new Entry() { { "MyTest", New Test() { Item = "Hello World"} } })
.ExecuteAsScalarAsync<Test>()
.Result;
Leads to the following error:
"The value for parameter 'MyTest' is of type 'ODataExample.Model.MyTest'. WriteValue can only write null, ODataComplexValue, ODataEnumValue and primitive types that are not Stream type."
Ok, cool, so the exception above is informative and I can work with this by doing
ODataComplexValue MyTest = new ODataComplexValue();
ODataProperty myP = new ODataProperty();
myP.Name = "Item";
myP.Value = "Hello World";
myCustomer.TypeName = "ODataExample.Model.MyTest";
myCustomer.Properties = new[] { myP };
So now if I pass myTest into the .Set I get a working call to my OData server
.Set(new Entry() { { "MyTest", MyTest )
So Obviously I can create a ODataComplexValue with something like
var tt = new ODataComplexValue()
{
TypeName = t.GetType().FullName,
Properties = s.GetType().GetProperties().Select<PropertyInfo,ODataProperty>(x => new ODataProperty { Name = x.Name, Value = x.GetValue(t) })
};
The above works great and I an just create an extension method from that to have
.Set(new Entry() { { "MyTest", New Test() { Item = "Hello World"} } }.ToOData())
However, I can't help but feel I'm missing something, if the solution is this simple, why is Simple.OData.Client not just doing this for me? Is is there already an extension method or utility I should be using to get the same above result.
Just incase its required my webapi routing is as followed :
var function = builder.EntityType<Account>().Action("Testing");
function.Namespace = "Transaction";
function.Parameter<Test>("MyTest");
function.ReturnsFromEntitySet<Test>("Test");

Categories