I have an APIM policy to send a request and response to an Azure Event Hub:
<outbound>
<log-to-eventhub logger-id="my-logger">#{
var request = context.Request.Body.As<string>(preserveContent: true);
var response = context.Response.Body?.As<string>(preserveContent: true);
var json = new JObject(
new JProperty("DateTimeStamp", DateTime.UtcNow),
new JProperty("IpAddress",context.Request.IpAddress),
new JProperty("RequestBody", request),
new JProperty("ResponseBody", response),
new JProperty("ResponseCode", context.Response.StatusCode)
);
return json.ToString();
}</log-to-eventhub>
<base />
</outbound>
A request made via Postman or any other client shows the response has the correct body but in the code above context.Response.Body is always null, hence the null check. The message shows up in the event hub fine with a correct request message but null response.
There are other policies on the API but this is the only outbound policy. What could be causing this?
Edit with full Trace message taking out the null check and using JObject as suggested.
log-to-eventhub (0.405 ms)
{
"messages": [
{
"message": "Expression evaluation failed.",
"expression": "\n var request = context.Request.Body.As<string>(preserveContent: true);\n var response = context.Response.Body.As<JObject>(preserveContent: true);\n\n var json = new JObject(\n new JProperty(\"DateTimeStamp\", DateTime.UtcNow),\n new JProperty(\"ServiceName\",context.Deployment.ServiceName),\n new JProperty(\"RequestId\",context.RequestId),\n new JProperty(\"IpAddress\",context.Request.IpAddress),\n new JProperty(\"OperationName\",context.Operation.Name),\n new JProperty(\"RequestBody\", request),\n new JProperty(\"ResponseBody\", response.ToString()),\n new JProperty(\"ResponseCode\", context.Response.StatusCode)\n );\n \n return json.ToString();\n ",
"details": "Object reference not set to an instance of an object."
},
"Expression evaluation failed. Object reference not set to an instance of an object.",
"Object reference not set to an instance of an object."
]
}
In place of As<string> try using As<JObject> which would return json JObject.
var response = context.Response.Body.As<JObject>(preserveContent: true);
JObject() Initializes a new instance of the JObject class.
for more information check https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Linq_JObject.htm
Related
I'm facing a behavior in Minimal API that I can't understand.Consider the following simple Minimal API:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseExceptionHandler((exceptionApp) =>
{
exceptionApp.Run(async context =>
{
context.Response.ContentType = MediaTypeNames.Text.Plain;
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
if (feature?.Error is BadHttpRequestException ex)
{
context.Response.StatusCode = 400;
var message =
(ex.InnerException is JsonException)
? "The request body is an invalid JSON"
: "Bad Request";
await context.Response.WriteAsync(message);
}
else
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("There is a problem occured");
}
});
});
app.MapPost("/models", (Model model) => Results.Created(string.Empty, model));
app.Run();
public record Model(int Value, string Title);
When I run the application in the Development environment, and pass an invalid JSON like
{
"value": 1,
"Title": Model #1
}
the custom exception handler is called and I have to control the behavior of the API. But whenever
I run the application in the Production environment, the framework automatically returns a
"bad request" response without letting me control the response.
Could anyone explain this behavior to me? I really need my exception handler to handle invalid input
JSON exceptions.
Thanks
After digging into ASP.Net Core source code for a while, I found that the following line resolves the issue.
builder.Services.Configure<RouteHandlerOptions>(o => o.ThrowOnBadRequest = true);
I am rewriting an app that used Google Contacts API (RIP) to use People API. I already have a refresh token. Previously, I created an instance of the OAuth2Parameters object, and used it to create an instance of the RequestSettings class to be passed to the ContactsRequest constructor
OAuth2Parameters oparams = new OAuth2Parameters
{
AccessToken = tokenData.access_token,
RefreshToken = tokenData.refresh_token,
ClientId = ClientId,
ClientSecret = ClientSecret,
AccessType = "offline",
ApprovalPrompt = "force",
Scope = _contactScope
};
if (string.IsNullOrWhiteSpace(oparams.AccessToken))
{
oparams.AccessToken = "xyz"; //it doesn't matter what this token is, it just can't be blank, it will get refreshed
OAuthUtil.RefreshAccessToken(oparams);
dataStore._storedResponse.access_token = oparams.AccessToken;
}
var settings = new RequestSettings("My App")
{
OAuth2Parameters = oparams
};
if (paging)
{
settings.PageSize = 50;
settings.AutoPaging = true;
}
return new ContactsRequest(settings);
I cannot figure out how to do the same in the new world of People API. I obviously need to use PeopleServiceService object, but its constructor takes an instance of the Initializer object, and I don't know out how I can initialize it with the refresh token and (possibly) access token.
Here's the official tutorial on how to do authentication with the .NET library for all Google APIs:
https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth
Here's a useful snippet from it that will also help with persisting the refresh token to a file and use it in future authentication attempts:
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { BooksService.Scope.Books },
"user", CancellationToken.None, new FileDataStore("Books.ListMyLibrary"));
}
// Create the service.
var service = new BooksService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Books API Sample",
});
var bookshelves = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
I'm trying to code a middleman API that logs calls and other details from internal users to an external API.
When I try to POST to the external API from my Controller, I get 415 unsupported media type.
I set up my client in the controller constructor like this:
client.BaseAddress = new Uri("https://restapi.***.com/customers/");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-AppSecretToken", Auth.APPSECRETTOKEN);
client.DefaultRequestHeaders.Add("X-AgreementGrantToken", Auth.AGREEMENTGRANTTOKEN);
My POST method looks like this:
var json = JsonConvert.SerializeObject(customer, Formatting.Indented);
using (var stringContent = new StringContent(json))
{
stringContent.Headers.ContentType.CharSet = "";
HttpResponseMessage response = await client.PostAsync(client.BaseAddress, stringContent);
if (!response.IsSuccessStatusCode)
{
return StatusCode((int)response.StatusCode);
}
}
return CreatedAtAction("GetCustomer", new { id = customer.ID }, customer);
I've been looking around and found a lot of comments telling me to use Stringcontent, but I also found a couple of responses saying ByteArrayContent - none of them work.
Can anyone help me?
EDIT: When I run the code with breakpoints it seems like some of the properties in the incoming customer object are set even though I didn't set them in my Postman call.
Example; the external API returns a customernumber when I give it the 5 properties that are obligatory. But when I call my internal API from Postman, sending only those 5 obligatory properties, it autopopulates the customernumber with a 0.
Could this be the source of the error? and how do I tell .net core to not autopopulate the customernumber?
EDIT2: I changed my stringContent to include encoding and used a different overload, so the using line now says
using (var stringContent = new StringContent(json, Encoding.UTF8, "application/json"))
And I removed
stringContent.Headers.ContentType.Charset = "";
to reflect the fact that I tried setting the encoding.
The return code changed from 415 to 400 Bad Request when I changed that.
EDIT3:
Tried NOT serializing with Json.Net, and instead used JObjects and Jproperties;
public async Task<ActionResult<Customer>> PostCustomer([FromBody]Customer customer)
{
JObject payload = new JObject(
new JProperty("currency", customer.Currency),
new JProperty("name", customer.Name),
new JProperty("customergroup",
new JObject(new JProperty("customergroupNumber",
customer.CustomerGroup.CustomerGroupNumber)
)),
new JProperty("paymentTerms",
new JObject(new JProperty("paymentTermsNumber",
customer.PaymentTerms.PaymentTermsNumber)
)),
new JProperty("vatZone",
new JObject(new JProperty("vatZoneNumber",
customer.VatZone.VatZoneNumber)
))
);
using (var stringContent = new StringContent(payload.ToString(), Encoding.UTF8, "application/json"))
{
HttpResponseMessage response = await client.PostAsync(client.BaseAddress, stringContent);
if (!response.IsSuccessStatusCode)
{
return StatusCode((int)response.StatusCode);
}
}
return CreatedAtAction("GetCustomer", new { id = customer.CustomerNumber }, customer);
}
Still 400 Bad Request
This is a case of capitalizing - simple really.
My POST request JSON had an object named customergroup - changed it to customerGroup, and it worked.
I'm trying to set up an inbound policy on an API in Azure API Manager that validates the JSON in POST request body before passing it on to the backend.
Should I be using a JSON schema and validate against that (how?) or if I should write my own code, using context.Request.Body to inspect each field in the request body or is it just plain wrong to try to validate a request body in APIM, should that be left to the backend?
We are currently validating in the API operations policy with a <set-body> to validate (and re-shape body if required). In this policy we're collecting all errors which we then throw with an exception:
<set-body>#{
var body = context.Request.Body.As<JObject>(true);
string businessSystemID = context.Request.Headers.GetValueOrDefault("SenderBusinessSystemID", "");
var result = new JObject();
string cleanUp = body.ToString().Replace("\r\n","");
var root = JObject.Parse(cleanUp);
var valid = false;
var errors = new JArray();
var returnValue = new JObject();
returnValue["errors"] = errors;
if(root["CostAllocation"] != null)
{
if(businessSystemID != string.Empty)
{
root["CostAllocation"]["SenderBusinessSystemID"] = businessSystemID;
}
if(root["CostAllocation"]["ReferenceID"] != null)
{
var referenceIDValidator = #"^[A-Z0-9]{0,35}$";
var referenceID = root["CostAllocation"]["ReferenceID"];
valid = new Regex(referenceIDValidator, RegexOptions.IgnoreCase).Match(referenceID.ToString()).Success;
if(!valid)
{
var error = new JObject();
error["property"] = "ReferenceID";
error["validator"] = referenceIDValidator;
error["value"] = referenceID;
error["message"] = "No valid 'ReferencedId'";
errors.Add(error);
}
}
if(root["CostAllocation"]["UserID"] != null)
{
var userIDValidator = #"^[\w]{4,12}$";
var userID = root["CostAllocation"]["UserID"];
valid = new Regex(userIDValidator, RegexOptions.IgnoreCase).Match(userID.ToString()).Success;
if(!valid)
{
var error = new JObject();
error["property"] = "UserID";
error["validator"] = userIDValidator;
error["value"] = userID;
error["message"] = "No valid 'UserID'";
errors.Add(error);
}
}
...
if(errors.Count > 0)
{
throw new Exception(returnValue.ToString());
}
return root.ToString(Newtonsoft.Json.Formatting.None);
}</set-body>
Exception is caught in <on-error>, handled and formatted:
<on-error>
<choose>
<when condition="#(context.LastError.Message.Contains("Expression evaluation failed")==true)">
<set-status code="400" reason="Bad request" />
</when>
<otherwise>
<set-status code="500" reason="Error" />
</otherwise>
</choose>
<set-body template="none">#{
return context.LastError.Message.Replace("Expression evaluation failed.","").Trim();
}</set-body>
<base />
</on-error>
We're aware that coding effort is high with this approach and would rather prefer for JSON schema checking natively supported directly in API Management. Having schema checking handled in a back-end and with that adding latency by passing on the request is not an option for us.
One option which I'm currently thinking of is parsing JSON schema in the API CI/CD process, generating a generic validation & replacing it in the API operations policy.
Validating payload is perfectly fine. Just be aware that doing so will require caching whole request body on APIM side which is not done by default. JSON schema validator is not available in policy expressions, so you have to resort to manual validation.
I am using exchange managed API and using push notification.
I am using below code
Uri uri = new Uri("http://domain.io/MyPage.aspx");
PushSubscription ps = service.SubscribeToPushNotifications(folder, uri, 1, "", EventType.Created, EventType.Modified, EventType.Deleted);
Now i get a hit on domain.io/MyPage.aspx when i change a event from calendar.
But now how i process that response ?
There is limited value in request header.
how could i know that which calendar, which service this request come.
Here is my answer. Using API Call is more simple.
public HttpResponseMessage ExchangeCalendar()
{
string itemId = string.Empty;
string subscriptionId = string.Empty;
string pushResponse = "OK";
string RESPONSE_OK = string.Empty;
HttpContent requestContent = Request.Content;
string eventData = requestContent.ReadAsStringAsync().Result;
XmlDocument doc = new XmlDocument();
doc.LoadXml(eventData);
subscriptionId = GetNodeValue(doc.GetElementsByTagName("t:SubscriptionId"));
itemId = GetNodeValue(doc.GetElementsByTagName("t:ItemId"));
calendarId = GetNodeValue(doc.GetElementsByTagName("t:FolderId"));
RESPONSE_OK = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope\"><soap:Body><SendNotificationResult xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\"><SubscriptionStatus>" + pushResponse + "</SubscriptionStatus></SendNotificationResult></soap:Body></soap:Envelope>";
return new HttpResponseMessage()
{
Content = new StringContent(RESPONSE_OK, Encoding.UTF8, "text/xml")
};
}
In very basic terms, after the SubscribeToPushNotifications call returns the PushSubscription there will be a subscription id that links the folder you subscribed to. Any notifications for that folder will contain the subscription id and the ItemId, as well as the type of notification, New, Changed, Moved, etc. You will need to maintain some kind of mapping of subscription id-to-folder, and then call EWS via GetItem to find the item in question.