NSwag: Generation of a C# client results in invalid code - c#

I am trying to generate a C# client for the Trello API. Therefore I have downloaded the Open API specification from https://developer.atlassian.com/cloud/trello/swagger.v3.json and run the following nswag command from the bash.
nswag openapi2csclient /input:swagger.v3.json \
/classname:TrelloClient \
/namespace:Integrations.Trello \
/output:TrelloClient.cs \
/GenerateClientInterfaces:True \
/GenerateExceptionClasses:True \
/GenerateClientClasses:True \
/DisposeHttpClient:False \
/OperationGenerationMode:SingleClientFromOperationId
The code generation completes without errors, but the generated code does not compile because it contains many errors. For example, some of the generated method names contain invalid expressions like =idAsync, or method signatures have ambiguous parameters (for instance, multiple key and token parameters of type string). The following method declaration has been generated for the GetMembers method, which is obviously the wrong syntax.
System.Threading.Tasks.Task<Member> GetMembers=idAsync(
string key, string token, string id, string actions, string boards, BoardBackgrounds? boardBackgrounds,
BoardsInvited? boardsInvited, BoardFields? boardsInvited_fields, bool? boardStars, string cards,
CustomBoardBackgrounds? customBoardBackgrounds, CustomEmoji2? customEmoji, CustomStickers? customStickers,
MemberFields? fields, string notifications, Organizations? organizations,
OrganizationFields? organization_fields, bool? organization_paid_account,
OrganizationsInvited? organizationsInvited, OrganizationFields? organizationsInvited_fields,
bool? paid_account, bool? savedSearches, Tokens? tokens);
Are there any special options that need to be set when processing an Open API specification document of version 3?

I was able to solve syntax issues regarding the get-members method in the generated code by changing the OperationGenerationMode from SingleClientFromOperationId to SingleClientFromPathSegments. This works as a workaround because the =id term only appears in the operationId.
I inspected Trello´s Open API specification document and found out that the definition of get-members contained the =id term in the operationId; not sure if this is wrong in the spec, or the generator is not able to handle that case correctly. The definition of the get-members method looks like that (foreshortened):
... "/members/{id}":
{
"get":
{
"tags": [],
"operationId":"get-members=id",
"parameters" ...
}
}
As a sidenote, the reason why I use the SingleClient... operation modes is that Nswag also adds GeneratedCode attributes to any partial class and interface which also results in invalid code (this attribute can only be applied once). The SingleClient... operation mode solves that.
What remains is the problem that NSwag generates methods with duplicated parameters, which has been reported as an issue, but is not solved yet; see https://github.com/RicoSuter/NSwag/issues/2560 for further information. Last but not least, I tried to remove the =id from the operationId and switched back to the SingleClientFromOperationId operation mode to check whether this does the trick, which is not the case.
I assume that NSwag is used by a broad audience which makes me think that this must be more related to the processed spec than Nswag. Thus, I looked up the spec for methods that have been reported by the C# compiler to have duplicated parameters. Here is an example.
...
{
"name": "token",
"in": "query",
"description": "The API token to use",
"required": true,
"schema": {
"$ref": "#/components/schemas/APIToken"
}
},
{
"name": "token",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "string"
}
}
...
So it seems that a method can have parameters of the same name, but they can appear in different places such a the query-string, or be part of the URL path. Then, it might be Nswag that produces the wrong output; if it is intended that both parameters can be sent to the API, then the generator should just prefix the names to avoid ambiguities, for instance, queryToken and pathToken, leaving it up to the developer to decide what parameter to use or to give a hint to the expected value (in case both parameters are required and expect different values).

Related

FHIR Client - How do I create a "valueReference"

I am using the NuGet package Hl7.Fhir.R4 to connect to the NHS ERS API. Part of the "CreateReferral" method requires an extension like this:
"extension": [
{
"url": "https://fhir.nhs.uk/STU3/StructureDefinition/Extension-eRS-Shortlist-SearchCriteria-1",
"valueReference": {
"reference": "#ServiceSearchCriteria-1"
}
}
]
I can't seem to find a model class that will create this output. Any ideas?
You should be able to add an extension to an element/resource with the AddExtension method. As parameters, you fill in the extension's url and a value of any FHIR datatype. For your extension, that would be a Reference, which is modelled by the ResourceReference class:
element.AddExtension("https://fhir.nhs.uk/STU3/StructureDefinition/Extension-eRS-Shortlist-SearchCriteria-1",
new ResourceReference("#ServiceSearchCriteria-1"));
When looking at the referral API, it seems to use FHIR STU3, so you may need to change your library to Hl7.Fhir.Stu3 as well to avoid mismatches in the resource's fields and other version differences. The above method should still work for adding your extension though.

AdaptiveCards throws an exception during .NET parsing

I currently encountered the following problem parsing an adaptive card.
This is the card:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "{{DATE(${$root.AdditionalData['DUE-DATE']},COMPACT)}}",
"wrap": true
}
]
}
This is the card-content:
{
"AdditionalData": {
"DUE-DATE": "2021-09-10T16:29:59Z"
}
}
Code:
c# on .NET Framework 4.7.2 where layout is a string with the above card and content is a string with the above card-content:
AdaptiveCardTemplate template = new AdaptiveCardTemplate(layout);
string cardJson = template.Expand(content);
AdaptiveCardParseResult card = AdaptiveCard.FromJson(cardJson);
And it crashes with:
AdaptiveCards.AdaptiveSerializationException: 'Error reading string. Unexpected token: Undefined. Path 'text', line 1, position 137.'
JsonReaderException: Error reading string. Unexpected token: Undefined. Path 'text', line 1, position 137.
The generated JSON on cardJson looks wrong to me at the text property:
{"type":"AdaptiveCard","$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.4","body":[{"type":"TextBlock","text":,"wrap":true}]}
I'm using the adaptive cards nuget packages:
AdaptiveCards 2.7.2
AdaptiveCards.Templating 1.2.
Did I encounter a parsing bug? The value for the text property should be 10.9.2021.
In the designer on adaptivecards.io everything works fine for some reason. Does anyone have a fix/workaround?
If you want the literal "text":"10.9.2021" to appear in your cardJson, use "${formatDateTime(AdditionalData['DUE-DATE'], 'd.M.yyyy')}" to generate the required value for your TextBlock:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "${formatDateTime(AdditionalData['DUE-DATE'], 'd.M.yyyy')}",
"wrap": true
}
]
}
This causes all date formatting to be performed by the AdaptiveCardTemplate and results in:
{
"type":"AdaptiveCard",
"$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
"version":"1.4",
"body":[
{
"type":"TextBlock",
"text":"10.9.2021",
"wrap":true
}
]
}
Demo fiddle #1 here.
If you would prefer "{{DATE(2021-09-10T16:29:59Z, COMPACT)}}" in your cardJson which delegates date formatting to the TextBlock, use:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "{{DATE(${AdditionalData['DUE-DATE']}, COMPACT)}}",
"wrap": true
}
]
}
Which results in
"text": "{{DATE(2021-09-10T16:29:59Z, COMPACT)}}",
Demo fiddle #2 here.
Notes:
According to the Microsoft documentation:
Use Dot-notation to access sub-objects of an object hierarchy. E.g., ${myParent.myChild}
Use Indexer syntax to retrieve properties by key or items in an array. E.g., ${myArray[0]}
But, when accessing an object property with a hyphen (or some other reserved operator) in its name, it is apparently necessary to use the Indexer syntax ['DUE-DATE'] instead of Dot‑notation to retrieve its value, passing the property name inside a single-quoted string as the indexer.
According to the docs
There are a few reserved keywords to access various binding scopes. ...
"$root": "The root data object. Useful when iterating to escape to parent object",
Thus you do not need to use $root when accessing properties of the currently scoped object (which is, by default, the root) when using Dot-notation. If for whatever reason you need or want to address the root object directly, you may use $root like so:
"text": "${formatDateTime($root.AdditionalData['DUE-DATE'], 'd.M.yyyy')}",
Demo fiddle #3 here.
However, it seems that using $root in combination with {{DATE()}} causes malformed JSON to be generated. I.e.
"text": "{{DATE(${$root.AdditionalData['DUE-DATE']}, COMPACT)}}",
results in "text":, as indicated in your question.
Demo fiddle #4 here.
This looks to be a bug in the framework. Possibly the parser is choking on the sequence of tokens ${$, as your issue somewhat resembles Issue #6026: [Authoring][.NET][Templating] Inconsistency in accessing $root inside a $when property in adaptive card templating which reports a failure to parse "$when": "${$root.UserName != null}" correctly.
You can avoid the problem either by omitting $root entirely, or by wrapping $root.AdditionalData['DUE-DATE'] in an additional formatDateTime() like so:
"text": "{{DATE(${formatDateTime($root.AdditionalData['DUE-DATE'])}, COMPACT)}}",
Resulting in
"text": "{{DATE(2021-09-10T16:29:59.000Z, COMPACT)}}",
Demo fiddle #5 here.
From the documentation page Adaptive Card Templating SDKs: Troubleshooting:
Q. Why date/time in RFC 3389 format e.g "2017-02-14T06:08:00Z" when used with template doesn't works with TIME/DATE functions?
A. .NET sdk nuget version 1.0.0-rc.0 exhibits this behavior. this behavior is corrected in the subsequent releases... Please use formatDateTime() function to format the date/time string to RFC 3389 as seen in this example, or you can bypass TIME/DATE functions, and just use formatDateTime(). For more information on formatDateTime(), please go here.
While this recommendation to use formatDateTime was to fix a problem in 1.0.0-rc.0, the trick also resolves the issue mentioned in note #2 above.

What types can you pass as a parameter to an admin-only C# Azure Function app?

What types are allowed as parameters for Azure Function apps written in C# that are only callable through the admin endpoint?
I've read lots of documentation and source code and I still don't know the answer. I'm looking for definitive references and clear explanations as to what that means and why, plus examples of how to implement functions with other parameter types.
I'm wondering if the Azure team expect you to accept a string of JSON and parse it yourself into proper types, but the documentation I've found doesn't say.
Additional context
The specific function I am working on is to be called only via the admin interface so does not have any Http etc bindings.
[NoAutomaticTrigger]
[FunctionName(nameof(SomeFunctionName))]
public async Task SomeFunctionName(ParameterTypeHere someParameterName)
{
...
What can I put instead of ParameterTypeHere?
The specific use I have (this time) is that I want to pass something like List<Guid> or Guid[], I don't mind if I have to wrap it in a class or something but nothing I tried worked so I've ended up splitting a string on comma and parsing out the guids which seems like a poor solution.
Currently I've got a string parameter and am calling it with:
$ curl -v http://localhost:7071/admin/functions/SomeFunctionName \
-d '{"input": "699F3073-9BFD-4DA7-9E61-4E6564D032EC,197DA362-C281-4E0F-BB92-8759F7A5B4B4"}' \
-H "Content-Type:application/json"
Research so far
Things I've already looked at that leave me still unsure what I can use beyond string for more complex inputs:
Azure Functions: Generic type as input parameter
https://github.com/Azure/Azure-Functions/issues/735
Azure Functions error - Cannot bind parameter to type String
Cannot bind parameter, cause the parameter type is not supported by the binding (HttpTrigger of Azure Functions)
Can not bind ILogger in azure function v1
https://learn.microsoft.com/en-us/azure/azure-functions/functions-manually-run-non-http
https://github.com/Azure/azure-functions-host/blob/dev/src/WebJobs.Script/Binding/Manual/ManualTriggerAttributeBindingProvider.cs#L39
https://github.com/Azure/azure-functions-host/blob/9b2fa0f3ea69c41ce819f046213eab4f40a5db5f/src/WebJobs.Script/Binding/StreamValueBinder.cs#L24-L30
https://github.com/Azure/azure-functions-host/blob/9b2fa0f3ea69c41ce819f046213eab4f40a5db5f/src/WebJobs.Script/Utility.cs#L245
ServiceBusTrigger with enqueueTimeUtc argument fails when triggered via HTTP endpoint
The parameter name is ignored, and you have to pass it in with the name "input" regardless of the actual parameter name. Just another thing to trip over.
Even more context
If you're wondering why you'd ever want an admin-only function, this was for a one-off job to be run by someone else who has access to the admin endpoints. It appeared to be the simplest thing that could work. An HttpTrigger would have been fine, it just appeared to violate YAGNI.
Some weeks ago, I tested how to convert an API using functions with special attention to DI (not shown in below example) and validation. This may not be a direct answer to your question, but it shows that plain model classes may be used.
public class MyRequestModel
{
[Display(Name = "Message")]
[Required, MinLength(3)]
public string Message { get; set; }
}
public static class MyHttpExample
{
[FunctionName(nameof(MyHttpExample))]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "test/{message}")] MyRequestModel req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateObject(req, new ValidationContext(req, null, null), validationResults, true))
{
return new BadRequestObjectResult(validationResults);
}
var responseMessage = $"Hello, {req.Message}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
}
More information on Azure Functions binding expression patterns
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-expressions-patterns
When reading the above doc, keep in mind that function.json is generated from annotations when using C#, but doc shows what's available. Generated function.json for the above example:
{
"generatedBy": "Microsoft.NET.Sdk.Functions-3.0.11",
"configurationSource": "attributes",
"bindings": [
{
"type": "httpTrigger",
"route": "test",
"methods": [
"get"
],
"authLevel": "function",
"name": "req"
}
],
"disabled": false,
"scriptFile": "../bin/MyFunctions.dll",
"entryPoint": "MyFunctions.MyHttpExample.Run"
}
See also https://stackoverflow.com/a/54489040/14072498

Invalid cache key parameter specified when enabling caching for a path parameter in AWS API Gateway

I have a serverless web API (API Gateway + Lambda) that I have built in C# and deployed via Visual Studio. This is achieved via a serverless.yml file that auto-creates a CloudFormation template, then that template is applied to create the API stack.
Once my stack is deployed, I have gone into the AWS Console to enable caching on one of the path parameters, but get this error:
!https://ibb.co/B4wmRRj
I'm aware of this post https://forums.aws.amazon.com/thread.jspa?messageID=711315&#711315 which details a similar but different issue where the user can't uncheck caching. My issue is I can't enable it to begin with. I also don't understand the steps provided to resolve the issue within that post. There is mention of using the AWS CLI, but not what commands to use, or what to do exactly. I have also done some reading on how to enable caching through the serverless.yml template itself, or cloud formation, but the examples I find online don't seem to match up in any way to the structure of my serverless file or resulting CF template. (I can provide examples if required). I just want to be able to enable caching on path parameters. I have been able to enable caching globally on the API stage, but that won't help me unless I can get the caching to be sensitive to different path parameters.
serverless.yml
"GetTableResponse" : {
"Type" : "AWS::Serverless::Function",
"Properties": {
"Handler": "AWSServerlessInSiteDataGw::AWSServerlessInSiteDataGw.Functions::GetTableResponse",
"Runtime": "dotnetcore2.0",
"CodeUri": "",
"MemorySize": 256,
"Timeout": 30,
"Role": null,
"Policies": [ "AWSLambdaBasicExecutionRole","AWSLambdaVPCAccessExecutionRole","AmazonSSMFullAccess"],
"Events": {
"PutResource": {
"Type": "Api",
"Properties": {
"Path": "kata/table/get/{tableid}",
"Method": "GET"
}
}
}
}
}
},
"Outputs" : {
"ApiURL" : {
"Description" : "API endpoint URL for Prod environment",
"Value" : { "Fn::Sub" : "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" }
}
}
--Update Start--
The reason, you are getting Invalid cache key parameter specified error because you did not explicitly highlighted the path parameters section.
This is because, although the UI somehow extrapolated that there is a
path parameter, it has not been explicitly called out in the API
Gateway configuration.
I tested with below and was able to replicate the behavior on console. To resolve this, follow my Point 1 section full answer.
functions:
katatable:
handler: handler.katatable
events:
- http:
method: get
path: kata/table/get/{tableid}
--Update End--
Here you go. I still don't have your exact serverless.yml so I created a sample of mine similar to yours and tested it.
serverless.yml
functions:
katatable:
handler: handler.katatable
events:
- http:
method: get
path: kata/table/get/{tableid}
request:
parameters:
paths:
tableid: true
resources:
Resources:
ApiGatewayMethodKataTableGetTableidVarGet:
Properties:
Integration:
CacheKeyParameters:
- method.request.path.tableid
Above should make tableid path parameter is cached.
Explanation:
Point 1. You have to make sure in your events after your method and path, below section is created otherwise next resources section of CacheKeyParameters will fail. Note - boolean true means path parameter is required. Once you explicitly highlight path parameter, you should be able to enable caching via console as well without resources section.
request:
parameters:
paths:
tableid: true
Point 2. The resources section tells API Gateway to enable caching on tableid path parameter. This is nothing but serverless interpretation of CloudFormation template syntax. How did I get that I have to use ApiGatewayMethodKataTableGetTableidVarGet to make it working?. Just read below guidelines and tip to get the name.
https://serverless.com/framework/docs/providers/aws/guide/resources/
Tip: If you are unsure how a resource is named, that you want to
reference from your custom resources, you can issue a serverless
package. This will create the CloudFormation template for your service
in the .serverless folder (it is named
cloudformation-template-update-stack.json). Just open the file and
check for the generated resource name.
What does above mean? - First run serverless package without resources section and find .serverless folder in directory and open above mentioned json file. Look for AWS::ApiGateway::Method. you will get exact normalized name(ApiGatewayMethodKataTableGetTableidVarGet) syntax you can use in resources section.
Here are some references I used.
https://medium.com/#dougmoscrop/i-set-up-api-gateway-caching-here-are-some-things-that-surprised-me-7526d954fbe6
https://serverless.com/framework/docs/providers/aws/events/apigateway#request-parameters
PS - If you still need CLI steps to enable it, let me know.

C# Odata customize native metadata

I have a simple Odata V4 service that returns persons.
This service is not accessible directly:
it is accessible through Apigee, ie with http://myApigeeServer/Person
then Apigee is pointing on a load balancer, ie with http://myLoadBalancer/Person
finally there is actually two servers behind my load balancer virtual IP, ie with http://myFirstServer/Person and http://mySecondServer/Person
My problem is that we can see the real final server URL in the service response "#odata.context" metadata, so a call to http://myApigeeServer/Person('foo') can lead to two responses:
{
"#odata.context": "http://myFirstServer/$metadata#Person/$entity",
"FirstName": "John",
"LastName": "Doe",
"Phone#odata.type": "#Collection(String)",
"Phone": [
"+123456789"
],
}
Or
{
"#odata.context": "http://mySecondServer/$metadata#Person/$entity",
"FirstName": "John",
"LastName": "Doe",
"Phone#odata.type": "#Collection(String)",
"Phone": [
"+123456789"
],
}
I really must hide the final servers names. So my question is: is it possible to totally remove the "#odata.context" metadata or to customize it ? In my case customization would mean forcing the "#odata.context" metadata value with the Apigee URL.
[---EDIT----]
Based on the link proposed by Dylan Nicholson (thanks!), indeed it exists a hook on the ODataMediaTypeFormatter that enables to change the service URI base address.
But in my tests, it didn't work/I didn't manage to make it work for the #odata.context URI. So from links to links and tests to tests, I arrived here and lencharest's solution works perfectly well: using a custom URL helper that rewrites each link base address. I'll see in the future if it's too brutal...
#Max
To suppress the #odata.context, you can use "application/json;odata.metadata=none"

Categories

Resources