In my ASP.NET Core-6 Web API, I have a challenge. I have a 3rd party URL as given below:
URL: http://api.thirdpartycompany.com:2233/api/oauth/login
Request:
{
"username": to_be_shared,
"password": to_be_shared
}
Response:
‘{
"response_code": "00",
"response_description": "Success"
"data": {...},
"size": 0,
"access_token": "access_token",
"refresh_token": "refresh_token",
"expires_in": "18000000",
"token_type": "BEARER"
}’
Sample Access Token Request Call:
url = "http://api.thirdpartycompany.com:2233/api/oauth/login"
payload = '{
"username": to_be_shared,
"password": to_be_shared
}'
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data = payload)
I am to POST into that given url of the third party and get their response, including the access token.
So far, I have done this:
DTO:
public class OAuthLoginRequest
{
public string username { get; set; }
public string password { get; set; }
}
public class OAuthLoginResponse
{
public string response_code { get; set; }
public string response_description { get; set; }
public int size { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
public string expires_in { get; set; }
public string token_type { get; set; }
}
So far, this is what I have:
public class GenericResponseDto<T>
{
public string response_code { get; set; }
public string response_description { get; set; }
}
public interface IAuthService
{
Task<GenericResponseDto<object>> LoginUser(OAuthLoginRequest request);
}
public class AuthService : IAuthService
{
public async Task<GenericResponseDto<object>> LoginUser(OAuthLoginRequest request)
{
var response = new GenericResponseDto<object>();
return response;
}
}
DIServiceExtension:
public static class DIServiceExtension
{
public static void AddDependencyInjection(this IServiceCollection services)
{
// Add Service Injections Here -- Auth
services.AddScoped<IAuthService, AuthService>();
}
}
Program.cs
builder.Services.AddDependencyInjection();
Using HttpClient, How do I complete AuthService and when user enters the required username and password, submits the request then it gets the response including access token from the third party URL (http://api.thirdpartycompany.com:2233/api/oauth/login) as response?
How do I achieve this?
FWIW please use either JsonPropertyNameAttribute or JsonProperty depending on what serialization/ deserialization library you're using and keep the names of the properties in your C# classes C#-like (i.e PascalCase, not snake_case)
The answer is written assuming you did that, so I'll use the ResponseCode property instead of response_code
Now to the actual answer.
This is relatively simple, you just need to post your data to the third part API, read the result and return it up the call stack. Like so
public class AuthService : IAuthService
{
// Configure your HttpClient injection as described here:
// https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
private readonly HttpClient _httpClient;
public AuthService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<OAuthLoginResponse> LoginUser(OAuthLoginRequest request)
{
var postContent = new StringContent(
JsonSerializer.Serialize(request),
Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync("oauth/login", postContent);
if (!response.IsSuccessStatusCode)
throw new Exception("Third party API request failed");
await using var stream = await response.Content.ReadAsStreamAsync();
return JsonSerializer.DeserializeAsync<OAuthLoginResponse>(stream);
}
}
Instead of returning a OAuthLoginResponse directly, you could change your GenericResponseDto first of to something specific like OAuthResponseDto and secondly to have the fields you need, like
public class OAuthResponseDto
{
[JsonPropertyName("response_code")]
public string ResponseCode { get; set; }
[JsonPropertyName("response_description")]
public string ResponseDescription { get; set; }
// More properties, like AccessToken, RefreshToken, ExpiresIn...
}
And then replace the last two lines of the LoginUser with this:
await using var stream = await response.Content.ReadAsStreamAsync();
var response = JsonSerializer.DeserializeAsync<OAuthLoginResponse>(stream);
return new OAuthResponseDto
{
ResponseCode = response.ResponseCode,
ResponseDescription = response.ResponseDescription,
// rest of the mapping
};
Small aside, this is how you'd configure your HttpClient injection
services.AddHttpClient<IAuthService, AuthService>(client =>
{
client.BaseAddress = new Uri("http://api.thirdpartycompany.com:2233/api");
});
Note: I intentionally left out quite a bit of error checking to make this answer short and sweet, but it's still quite a long answer, you'll have to add that error checking in your implementation.
Note 2: I also don't 100% guarantee that all of this compiles first go, I did my best, but I wrote a large part of this in the Stack Overflow editor, there may be a missing parenthesis or comma or semicolon somewhere, trivial fixes
Related
I'm trying to call the business endpoint of Yelp's GraphQL api with my asp.net core mvc application using GraphQLHttpClient. I have the api and bearer token configured in my client instance. I followed the query structure here using business as the endpoint and I just wanted Id and Name fields from the data. When I call SendQueryAsync(query), I get a GraphQL Error from the response. I'm not sure if I'm making an improper httprequest and/or my query is written wrong. I couldn't find any YouTube videos, stackoverflow questions, or github projects regarding consuming Yelp's GraphQL api using C#. Any help is appreciated. Thank you! Below is my source code and attached response.
[Update: Resolved Issued]
There were a collection of issues. Added additional required fields with variables to YelpGraphQL query for GraphQL request. More about query structure and variable declaration is explained in this thread. Overrided the casing of the fields (ty Neil). Fixed the responsetype class and added the missing classes (ty Neil). Added searchconsumer class to controller via dependency injection. Also I will post copied text of exceptions next time.
Classes
public class Business
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
}
public class Search
{
[JsonPropertyName("business")]
public List<Business> business { get; set; }
}
public class SearchResponseType
{
[JsonPropertyName("search")]
public Search Search { get; set; }
}
public interface ISearchConsumer
{
public Task<List<Business>> GetAllBusinesses();
}
public class SearchConsumer : ISearchConsumer
{
private readonly ApplicationDbContext _dbContext;
public SearchConsumer(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<List<Business>> GetAllBusinesses()
{
var authorization = _dbContext.Authorizations.FirstOrDefault().Token;
var _client = new GraphQLHttpClient("https://api.yelp.com/v3/graphql", new NewtonsoftJsonSerializer());
_client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authorization);
var query = new GraphQLRequest
{
Query = #"
query($termId: String $locationId: String){
search(term:$termId location:$locationId) {
business {
id
name
}
}
}",
Variables = new
{
termId = "burrito",
locationId = "san francisco"
}
};
var response = await _client.SendQueryAsync<SearchResponseType>(query);
var businesses = response.Data.Search.business;
return businesses;
}
}
Controllers
public class YelpGraphQLController : Controller
{
private readonly ISearchConsumer _consumer;
public YelpGraphQLController(ISearchConsumer consumer)
{
_consumer = consumer;
}
public IActionResult Index()
{
return View();
}
[HttpGet]
public async Task<IActionResult> Get()
{
var businesses = await _consumer.GetAllBusinesses();
return Ok(businesses);
}
}
Program
ConfigureServices(builder.Services);
void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISearchConsumer, SearchConsumer>();
}
YelpGraphQL Json Data Example
{
"data": {
"search": {
"business": [
{
"id": "wGl_DyNxSv8KUtYgiuLhmA",
"name": "Bi-Rite Creamery"
},
{
"id": "lJAGnYzku5zSaLnQ_T6_GQ",
"name": "Brenda's French Soul Food"
}
]
}
}
}
Debug GraphQL Error
I'm guessing that the deserialization isn't working because of the casing of the fields vs your class, which you can override like so:
public class Business
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
}
public class ResponseBusinessCollectionType
{
[JsonPropertyName("businesses")]
public List<Business> Businesses { get; set; }
}
I've got data from an API, deserializing it to work with my model, but when I try to add that data to my database its only stores a blank object.I've tried passing through CrdResponse but that provides an exception that I can't get around.
public async Task <ActionResult> InsertCard(Card card)
{
List<Card> CardsInfo = new List<Card>();
var getUrl = System.Web.HttpContext.Current.Request.Url.AbsoluteUri;
var test = new Uri(getUrl);
var id = test.Segments.Last();
SingleResult += id;
using (var client = new HttpClient())
{
//passing service baseurl
client.BaseAddress = new Uri(SingleResult);
client.DefaultRequestHeaders.Clear();
//Define request data format
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Sending request to find web api REST service resource Getallcards using HTTPClient
HttpResponseMessage Res = await client.GetAsync(SingleResult);
//Checking the response is successful or not which is sent using HttpClient
if (Res.IsSuccessStatusCode)
{
//Storing the response details received from web api
var CrdResponse = Res.Content.ReadAsStringAsync().Result;
//Deserializing the response recieved from web api and storing in to the card list
CardsInfo = JsonConvert.DeserializeObject<List<Card>>(CrdResponse);
_context.Cards.Add(card);
_context.SaveChanges();
}
}
return RedirectToAction("Index", "API");
}
and my Model
public class Card
{
public int Id { get; set; }
public string Name { get; set; }
public int? Atk { get; set; }
public int? Def { get; set; }
public string Desc {get; set;}
public int? Level { get; set; }
public string Type { get; set; }
public string Attribute { get; set; }
[DisplayName("Image")]
public IList<Image> Card_Images { get; set; }
public IList<Deck> Deck { get; set; }
}
EDIT: CrdResponse contains:
[{"id":"11714098","name":"30,000-Year White Turtle","type":"Normal Monster","desc":"A huge turtle that has existed for more than 30,000 years.","atk":"1250","def":"2100","level":"5","race":"Aqua","attribute":"WATER","card_images":[{"id":"11714098","image_url":"https://storage.googleapis.com/ygoprodeck.com/pics/11714098.jpg","image_url_small":"https://storage.googleapis.com/ygoprodeck.com/pics_small/11714098.jpg"}],"card_prices":{"cardmarket_price":"0.00","tcgplayer_price":"0.00","ebay_price":"10.0","amazon_price":"0.00"}}]
which is all the info i am hoping to pull through
You are adding the 'Card' you pass through in the method parameter:
public async Task <ActionResult> InsertCard(Card card)
...
_context.Cards.Add(card);
I think what you want to do is pass through the card you are deserialising from the string response:
CardsInfo = JsonConvert.DeserializeObject<List<Card>>(CrdResponse);
foreach(Card c in CardsInfo)
{
_context.Cards.Add(c);
}
You forgot to await the result of the ReadAsStringAsync method, like so:
var CrdResponse = await Res.Content.ReadAsStringAsync();
As it is an async method it might not have completed the operation when you call result, that depends on the size of the string you are trying to read.
Hope to have helped!
Normally, serialized objects would be used from the services to the webapi calls but in this instance I have to use a json representation for the call.
The process would be to deserialize the json to the proper class, then process as usual.
HttpClient Put
Method is called from within a console app
public async Task<ApiMessage<string>> PutAsync(Uri baseEndpoint, string relativePath, Dictionary<string, string> headerInfo, string json)
{
HttpClient httpClient = new HttpClient();
if (headerInfo != null)
{
foreach (KeyValuePair<string, string> _header in headerInfo)
_httpClient.DefaultRequestHeaders.Add(_header.Key, _header.Value);
}
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json-patch+json"));
var content = new StringContent(json, Encoding.UTF8, "application/json-patch+json");
var response = await httpClient.PutAsync(CreateRequestUri(relativePath, baseEndpoint), content);
var data = await response.Content.ReadAsStringAsync();
...
}
Endpoint
The call never hits the endpoint. The endpoint is hit if I remove the [FromBody] tag but as expected, the parameter is null. There seems to be some sort of filtering happening.
[HttpPut()]
[Route("")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdatePaymentSync([FromBody] string paymentSyncJson)
{
if (string.IsNullOrEmpty(paymentSyncJson))
return BadRequest();
//hack: don't have access to models so need to send json rep
var paymentSync = JsonConvert.DeserializeObject<PaymentSync>(paymentSyncJson);
....
}
This is the json payload. I thought [FromBody] took care of simple types but this is proving me wrong.
{
"paymentSyncJson": {
"id": 10002,
"fileName": "Empty_20190101.csv",
"comments": "Empty File",
"processingDate": "2019-01-02T19:43:11.373",
"status": "E",
"createdDate": "2019-01-02T19:43:11.373",
"createdBy": "DAME",
"modifiedDate": null,
"modifiedBy": null,
"paymentSyncDetails": []
}
}
Just expanding on my Comment.
The OP did:
[HttpPut()]
[Route("")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdatePaymentSync([FromBody] string paymentSyncJson)
{
if (string.IsNullOrEmpty(paymentSyncJson))
return BadRequest();
//hack: don't have access to models so need to send json rep
var paymentSync = JsonConvert.DeserializeObject<PaymentSync>(paymentSyncJson);
....
}
Where they have put [FromBody] string paymentSyncJson, FromBody will try and deserialise into the type you specify, in this case string. I suggest doing:
public async Task<IActionResult> UpdatePaymentSync([FromBody] JObject paymentSyncJson)
Then you can change this line:
var paymentSync = JsonConvert.DeserializeObject<PaymentSync>(paymentSyncJson);
To:
var paymentSync = paymentSyncJson.ToObject<PaymentSync>();
Your payload is not a string, it's a json, that's why the runtime can't parse the body to your requested string paymentSyncJson.
To solve it, create a matching dto which reflects the json
public class PaymentDto
{
public PaymentSyncDto PaymentSyncJson { get; set; }
}
public class PaymentSyncDto
{
public int Id { get; set; }
public string FileName { get; set; }
public string Comments { get; set; }
public DateTime ProcessingDate { get; set; }
public string Status { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime ModifiedDate { get; set; }
public string ModifiedBy { get; set; }
public int[] PaymentSyncDetails { get; set; }
}
Then use it in the controller method to read the data from the request body
public async Task<IActionResult> UpdatePaymentSync([FromBody] PaymentDto payment)
Using a .Net Core 1.0 Lambda I want to be able to create a Lambda function which handles the PreSignUp trigger from an AWS Cognito User pool.
using Amazon.Lambda.Core;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
public class PreSignUp_SignUp
{
public string userPoolId { get; set; }
public const string EmailKey = "email";
public const string PhoneNumber = "phone_number";
public Dictionary<string,string> userAttributes { get; set; }
public Dictionary<string, string> validationData { get; set; }
}
public class PreSignup_SignUpResponse
{
public bool autoConfirmUser { get; set; }
}
public class Function
{
public PreSignup_SignUpResponse FunctionHandler(PreSignUp_SignUp input, ILambdaContext context)
{
return new PreSignup_SignUpResponse { autoConfirmUser = true };
}
}
Though the request succeeds and returns a response when invoking the Lambda with an example request of:
{
"datasetName": "datasetName",
"eventType": "SyncTrigger",
"region": "us-east-1",
"identityId": "identityId",
"datasetRecords": {
"SampleKey2": {
"newValue": "newValue2",
"oldValue": "oldValue2",
"op": "replace"
},
"SampleKey1": {
"newValue": "newValue1",
"oldValue": "oldValue1",
"op": "replace"
}
},
"identityPoolId": "identityPoolId",
"version": 2
}
When performing an actual SignUp via the .Net AmazonCognitoIdentityProviderClient I get back an error:
Amazon.CognitoIdentityProvider.Model.InvalidLambdaResponseException :
Unrecognizable lambda output
Which I'm guessing means I have not got the shape of the response (and possibly even request) correct.
Does anyone have an example of a .Net Lambda function that works for the PreSignUp trigger in AWS Cognito?
The cognito trigger requests/responses must contain the entire payload as specified in the Cognito trigger documentation:
http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html
I have found while diagnosing this issue the best place to start is by creating a function handler that takes a JObject and then logs and return's that same object e.g.
public JObject FunctionHandler(JObject input, ILambdaContext context)
{
context.Logger.LogLine("Input was: " + input);
return input;
}
This captures the payload in cloudwatch logs and then helps steer you towards the strongly typed structured required.
In my case for PreSignUp I ended up creating the following types to make a simple function which auto-verifies all supplied credentials.
public abstract class AbstractTriggerRequest
{
[JsonProperty("userAttributes")]
public Dictionary<string, string> UserAttributes { get; set; }
}
public abstract class AbstractTriggerResponse
{
}
public class TriggerCallerContext
{
[JsonProperty("awsSdkVersion")]
public string AwsSdkVersion { get; set; }
[JsonProperty("clientId")]
public string ClientId { get; set; }
}
public abstract class AbstractTriggerBase<TRequest, TResponse>
where TRequest: AbstractTriggerRequest
where TResponse: AbstractTriggerResponse
{
[JsonProperty("version")]
public int Version { get; set; }
[JsonProperty("triggerSource")]
public string TriggerSource { get; set; }
[JsonProperty("region")]
public string Region { get; set; }
[JsonProperty("userPoolId")]
public string UserPoolId { get; set; }
[JsonProperty("callerContext")]
public TriggerCallerContext CallerContext { get; set; }
[JsonProperty("request")]
public TRequest Request { get; set; }
[JsonProperty("response")]
public TResponse Response { get; set; }
[JsonProperty("userName", NullValueHandling = NullValueHandling.Ignore)]
public string UserName { get; set; }
}
public class PreSignUpSignUpRequest : AbstractTriggerRequest
{
[JsonProperty("validationData")]
public Dictionary<string,string> ValidationData { get; set; }
}
The Lambda function then ends up with the following signature:
public class Function
{
public PreSignUp_SignUp FunctionHandler(PreSignUp_SignUp input, ILambdaContext context)
{
context.Logger.LogLine("Auto-confirming everything!");
input.Response = new PreSignUpSignUpResponse {
AutoConfirmUser = true,
// you can only auto-verify email or phone if it's present in the user attributes
AutoVerifyEmail = input.Request.UserAttributes.ContainsKey("email"),
AutoVerifyPhone = input.Request.UserAttributes.ContainsKey("phone_number")
};
return input;
}
}
Hopefully this helps anyone else running into issues writing Lambda triggers for Cognito.
The previous two responses are now inaccurate unless you still use the old, less performant Amazon.Lambda.Serialization.Json.JsonSerializer. This old serializer uses Newtonsoft.Json while the new Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer implements the recent System.Text.Json.
As a result, a JObject parameter is no longer appropriate and should instead be replaced with JsonElement. If you try to use JObject with this new serializer, you will get an error since the serializer doesn't know how to deal with this object.
You should read this to gain a better understanding of how it all works, but you access properties of the JsonElement using GetProperty("[insert property name here]").
For example:
public async Task<JsonElement> FunctionHandler(JsonElement input, ILambdaContext context)
{
var request = input.GetProperty("request");
var userAttributes = request.GetProperty("userAttributes");
string email = userAttributes.GetProperty("email").GetString();
return input;
}
This way, you don't need to construct entire classes to accommodate the required request and response parameters, just get and set the properties you need.
There is already another great answer in here. However I'm not a expert .NET developer so this solution makes more sense to me.
class AutoVerifyEmail
{
public AutoVerifyEmail() { }
public JObject AutoVerifyEmailPreSignup(JObject input, ILambdaContext context)
{
//Console.Write(input); //Print Input
input["response"]["autoVerifyEmail"] = true;
input["response"]["autoConfirmUser"] = true;
return input;
}
}
I generate a C# class for a json source using json2csharp.com. My json is:
{
"email_verified": true,
"user_id": "gg2323",
"app_metadata": {
"tc_app_user": {
"user_guid": "c0fb150f6er344df98ea3a06114e1e4a",
"cto_admin_a_user_id": "551294d4f6cfb46e65a5aq71",
"lang": "EN",
"country": "USA",
"disabled": false
}
and my resulting C# is:
public class TcAppUser
{
public string user_guid { get; set; }
public string cto_admin_a_user_id { get; set; }
public string lang { get; set; }
public string country { get; set; }
public bool disabled { get; set; }
}
public class AppMetadata
{
public TcAppUser tc_app_user { get; set; }
public int logins_count { get; set; }
}
public class RootObject
{
public bool email_verified { get; set; }
public string user_id { get; set; }
public AppMetadata app_metadata { get; set; }
}
Using the .NET HttpClient GET, I can read into this C# structure from the JSON API quite nicely. Going the other way (POST, PATCH) poses a problem: my app_metadata property name is dropped in the generated JSON output when I use a common approach like:
//Would be nice: var contentIn = new ObjectContent<string>(RootObjectInstance.app_metadata, new JsonMediaTypeFormatter());
string json = JsonConvert.SerializeObject(RootObjectInstance.app_metadata);
HttpResponseMessage response = await hclient.PatchAsync("api/users/" + user_id, new StringContent(json, Encoding.UTF8, "application/json"));
The resulting JSON is now:
{
"tc_app_user": {
"lang": "en-en",
"country": "GER",
"disabled": false
}
}
My quick hack is to use the following additional wrapper to dynamically repackage the app_metadata property so it has the same format going out that it had coming in. The rest remains the same as above:
dynamic wireFormatFix = new ExpandoObject();
wireFormatFix.app_metadata = usr.app_metadata;
string json = JsonConvert.SerializeObject(wireFormatFix);
Now my JSON output corresponds to the JSON input. My question: what is best-practice to achieve symmetric json input and output here without a pesky format fix?
EDIT: If I try to PATCH the entire structure (RootObjectInstance instead of RootObjecInstance.app_metadata) I get:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Payload validation error: 'Additional properties not allowed: 'user_id'."
}
So, I must either send the app_metadata subset/property of the C# RootObject, properly packaged, or I must selectively delete fields from the RootObject to meet the API's requirements.
Thanks!
The root app_metadata tag is being removed from your JSON because you're simply not serializing it. This:
string json = JsonConvert.SerializeObject(RootObjectInstance.app_metadata);
Will serialize everything that is inside app_metadata.
If you serialized the entire object graph, you wouldn't need to patch anything:
string json = JsonConvert.SerializeObject(RootObjectInstance);
As a side note, you should follow C# naming conventions. You can use JsonProperty to help you with that.
Edit:
Ok, after your edit i see the actual problem. You're calling an API by user_id in your query string, and you also have a user_id property inside your object. This seems like you need two different objects for the job.
You have a couple of possibilities:
Create an object hierarchy:
public class BaseObject
{
[JsonProperty(email_verified)]
public bool EmailVerified { get; set; }
[JsonProperty(app_metadata)]
public AppMetadata AppMetadata { get; set; }
}
public class ExtendedObject : BaseObject
{
[JsonProperty(user_id)]
public string UserId { get; set; }
}
And then use the base type to serialize the data:
var baseObj = new BaseObject(); // Fill the object properties.
var json = JsonConvert.SerializeObject(intermidiateObj);
HttpResponseMessage response = await hclient.PatchAsync("api/users/" +
user_id,
new StringContent(json,
Encoding.UTF8,
"application/json"));
Use an anonymous object which includes only properties you actually need:
var intermidiateObj = new { app_metadata = usr.app_metadata };
var json = JsonConvert.SerializeObject(intermidiateObj);
HttpResponseMessage response = await hclient.PatchAsync("api/users/" +
user_id,
new StringContent(json,
Encoding.UTF8,
"application/json"));