C# Odata customize native metadata - c#

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"

Related

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.

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

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).

AspNetCore Health Checks UI Reporting Wrongly

I've been having a little issue implementing AspNetCore.HealthChecks.UI correctly. I have two endpoints that run health checks using the "live" and "ready" as tags. Both endpoints work work as expected but after implementing the HealthChecksUI, The health check results displayed is always "Unhealthy
An error occurred while sending the request." even though the endpoint returns "Healthy" from postman. Please see screenshots and relevant code snippets and configurations.
AppSettings Configuration
"HealthChecksUI": {
"HealthChecks": [
{
"Name": "Crowd Funding App",
"Uri": "http://localhost:5001/healthui"
}
],
"EvaluationTimeinSeconds": 10,
"MinimumSecondsBetweenFailureNotifications": 60
}
In the Configure function of the startup class, I have the following code.

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.

Add claims to user through azure ad b2c using graph api

I have a web application and am planning to move all the authentication to Azure AD b2c.
I need to create user through Graph API and now i can create user through Graph API, i also need to add claims
When adding user or updating user, after further googling i found out, you need to add extension property, I tried adding extensions, but it seems to be not working for me. Any help will greatly appreciated.
I am using the sample provided by MSFT https://github.com/AzureADQuickStarts/B2C-GraphAPI-DotNet . I can now create an extension with the below JSON
{
"accountEnabled": true,
"signInNames": [
{
"type": "emailAddress",
"value": "kart.kala1#test.com"
}
],
"creationType": "LocalAccount",
"displayName": "Joe Consumer",
"mailNickname": "joec",
"passwordProfile": {
"password": "P#$$word!",
"forceChangePasswordNextLogin": false
},
"passwordPolicies": "DisablePasswordExpiration",
"city": "San Diego",
"country": null,
"facsimileTelephoneNumber": null,
"givenName": "Joe",
"mail": null,
"mobile": null,
"otherMails": [],
"postalCode": "92130",
"preferredLanguage": null,
"state": "California",
"streetAddress": null,
"surname": "Consumer",
"telephoneNumber": null,
"extension_a550f811ccfe41f19e895f7931f7a28a_admin": "admin1"
}
Above extension_a550f811ccfe41f19e895f7931f7a28a_admin": "admin1" property worked for me to add extension, but i created a user thru sign-signin profile in azure portal and then added the details which is got from "GET-USER" of another user and re-used the name. What is the alpha numeric value? is it created run time or can it be resolved using any value from the user data ?. I will have two accounts stage and production and i cannot be resolving the value or changing it runtime.
Don't have the rep to comment so need to put it in answer form.
I am not entirely sure what the question is, but I have successfully used the Graph API to set/get the extension data (for example using the JSON attribute extension_a550f811ccfe41f19e895f7931f7a28a_admin).
I am not sure how you are getting the user info but it seems as though it might be by name, which may not be unique.
For getting/setting B2C user info you need to use the objectId of the user as it is guaranteed unique. This means you need to store that objectId in your database/storage for that user.

Categories

Resources