I am facing this issue for the Azure API Gateway implemented in few of our products :
Message Expression evaluation failed. Object reference not set to an instance of an object.
Exception type GatewayError
Failed method set-variable[2]
{ "statusCode": 500, "message": "Internal server error", "activityId": "bbcf1e2d-c3bd-4020-8a40-cffd99a7f09e" }
This is random. As per my analysis, when I get ResponseCode as 500 or 0 from Back end API, my policy expression evaluation fails where I am capturing RequestMethod, ResponseBody, StatusCode Received, RequestBody, RequestURL and storing the same in a variable.
I read a few troubleshooting articles and have confirmed that the BASE tag is not missing from anywhere under policies. Apparently, this could cause some issues.
Trying to fix the issue, I have now used a conditional operator while setting each variable however the same error still occurs.
<choose>
<when condition="#((string)(context.Variables["RequestOperation"]) == "POST")">
<set-variable name="RequestedBody" value="#(context.Request.Body == null ? " Unable to capture":
context.Request.Body.As<string>(preserveContent: true))" />
</when>
<otherwise>
<set-variable name="RequestedQueryParameters" value="#(context.Request.Url.QueryString == null ||
context.Request.Url.QueryString == "" ? "Unable to capture":(string)context.Request.Url.QueryString)" />
</otherwise>
</choose>
This code is written in the INBOUND policy under the BASE tag.
Policy expression evaluation is failing with the following error message:
Message Expression evaluation failed. Object reference not set to an instance of an object.
Exception type GatewayError
Failed method set-variable[2]
Can someone please help me to solve this problem.
set-variable[2] indicates there is issue in second instance of statement in your policy. I'm unsure whether instance count also . Something you can test very quickly by executing policy with and without and see if instance count changes.
I debug such issues by using try/catch and setting variable or output trace and then use APIMs Test tool to identify issues. Here is what you can try:
`<set-variable name="RequestedQueryParameters" value="#{
try {
return string.IsNullOrEmpty(context.Request.Url.QueryString? "Unable to capture" :(string)context.Request.Url.QueryString);
}
catch (Exception ex)
{
return ex.Message;
}
return string.Empty;
}" />
`
HTH.
Related
We have an application that requires some fields to be present. If those fields aren't present we will return a 400 response explaining what is missing in a proper error message. Adding APIM to the mix complicates it a lot it seems. Since APIM know that the field is required it looks like it will short curcuit and return 404 with a generic message instead of our self explanatory message of what is wrong.
Is it a way to turn of this functionality for APIM?
I'm getting the same issue and I endup changing my approach. What I did was to configure it on the Application side and use FluentValidation to make the querystring parameters required. So, my model now looks something like this:
using FluentValidation;
public class UrlQueryParameters
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
public class UrlQueryParametersValidator : AbstractValidator<UrlQueryParameters>
{
public UrlQueryParametersValidator()
{
RuleFor(o => o.PropertyA)
.NotEmpty()
.WithMessage("The 'PropertyA' parameter was missing or a value was not provided.");
RuleFor(o => o.PropertyB)
.NotEmpty()
.WithMessage("The 'PropertyB' parameter was missing or a value was not provided.");
}
}
The preceding code defines a couple of validation rules with custom messages for PropertyA and PropertyB properties.
Now, enable FluentValidation as the default validation mechanism for our application by adding the following code at ConfigureServices method of Startup.cs file:
public void ConfigureServices(IServiceCollection services) {
// Rest of the code omitted for brevity
services
.AddControllers()
.AddFluentValidation(fv =>
{
fv.DisableDataAnnotationsValidation = true;
// The following line registers ALL public validators in the assembly containing UrlQueryParametersValidator
// There is no need to register additional validators from this assembly.
fv.RegisterValidatorsFromAssemblyContaining<UrlQueryParametersValidator>(lifetime: ServiceLifetime.Singleton);
});
}
At this point, your API endpoints should validate the required parameters from the request and APIM should not short-circuit the request by throwing 404 Not Found when you try to access /api/foo/{id}.
The reason why this works because Swashbuckle doesn't automatically import validation rules from FluentValidation. Meaning, the properties PropertyA and PropertyB won't be marked as required when viewing them in the Swagger UI. This is the downside for this approach as the required querystring parameters from the Swagger UI will not be marked as required which could be confusing to consumers. But to me, returning the correct StatusCode with meaningful message to consumers is more important and that's why I will stick to this workaround for the time being. You could try using the MicroElements.Swashbuckle.FluentValidation to altleast set/marked the parameters as required in the Swagger UI schema. But that's just about it.
I blogged about this it here: Dirty Hack on Making the Required QueryString Params to Work in Azure APIM
At API/Product/Global level policy add on-error section, use choose policy to check if operation was found or not:
<choose>
<when condition="#(context.LastError.Source == "configuration" && context.LastError.Reason == "OperationNotFound")">
<return-response>
<set-status code="400" reason="Bad Request" />
</return-response>
</when>
</choose>
Is there any way which can map my response of API into the required format in azure APIM
e.g. if my API gave a response like
customer: {
"id" : 123
}
the output I will be needing is
customer: {
"customerID": 123
}
Is there any way without using a set-body policy?
or is there any way where I can define logic which can map Id value to customerID
There are two options:
Simple JSON
If you have simple JSON as you have shown in the question, then Find and Replace transformation policy can be helpful.
Please note that this policy would just perform string replace. It does not know anything about JSON format. So you have to be extra careful while replacing anything.
But I assume the JSON might not be that simple in real world scenarios.
Complex JSON
You can implement your custom logic by using Set Body Transformation Policy.
Code would be something like below. You can get the JObject and then query and update it.
<choose>
<when condition="#(context.Response.StatusCode == 200">
<set-body>
#{
JObject inBody = context.Request.Body.As<JObject>();
// Razor view syntax so you can write code as per your need
JObject customer= (JObject)inBody["customer"];
var customerIdValue = ((string)customer["id"]);
customer["CustomerId"] = customerId;
customer.Property("id").Remove();
// assuming customer has a description property
customer.Property("description").AddAfterSelf(new JProperty("customerId", customerIdValue));
return inBody.ToString();
}
</set-body>
</when>
</choose>
You could use the Find and Replace String in Body policy for a simple solution.
Could also use set-body with liquid template in outbound of your operation:
<set-body template="liquid">{
"customer": {
"customerID": {{body.customer.id}}
}
}</set-body>
I am getting the following wherever I am using nlapiLoadRecord or nlapiCreateRecord in the suitescript(1.0):
"error": {
"code": "UNEXPECTED_ERROR",
"message": "syntax error (NLRecordScripting.scriptInit$lib#151)"
}
Following is the code snippet
if(datain.internalid != null){
record = nlapiLoadRecord('supportcase',datain.internalid);
}else{
record = nlapiCreateRecord('supportcase');
}
Can anyone help with this?
There is a User Event running on beforeLoad that has an error in it; based on the error, I would look for a file containing a reference like NLRecordScripting.scriptInit on line 151.
I need to set a timeout at the api level, I used this
<forward-request timeout="3">
I added wait() in the micro-service to test this change, and it is working as expected.
The problem is in the error returned, it is so generic
{
"statusCode": 500,
"message": "Internal server error",
"activityId": "xxxxxxxx"
}
How to make it be more specific (timeout error)?
Update 1:
I have found a solution for this problem by adding this
<on-error>
<set-body>
#(
new JObject(
new JProperty("MsgRsHdr",new JObject(new JProperty("status", "ERROR"))),
new JProperty("content",new JObject(
new JProperty("errors",
new JArray(
new JObject(
new JProperty("code", context.Response.StatusCode.ToString()),
new JProperty("message", context.LastError.Message),
new JProperty("Reason", context.LastError.Reason),
new JProperty("Source", context.LastError.Source),
new JProperty("Scope", context.LastError.Scope),
new JProperty("Section", context.LastError.Section),
new JProperty("Path", context.LastError.Path),
new JProperty("PolicyId", context.LastError.PolicyId),
new JProperty("type", "Error")
)
)
),
new JProperty("timestamp",DateTime.UtcNow)
)
)
).ToString()
)
</set-body>
</on-error>
For me this seems to be a work-around, and not the intended way to return a more meaningful response.
The idea behind default behavior is that you're in control of how much detail you want to expose to clients calling your API. Clients are not always in your control. Look into set-body with template to make error response easier to read in policy.
I am trying to access messages for a specific folder which used to work. For some reason passing "?" in the url is rejected as "illegal character" in our api so I I have encoded the "?" to "%3F" and the final url looks like this:
https://graph.microsoft.com/v1.0/me/MailFolders/INBOX/messages?$filter=From/EmailAddress/Address eq 'alerts-noreply#mail.windowsazure.com'
The header contains the following.
("Authorization", "Bearer " + token)
("Content-Type", "application/json")
("Accept", "application/json")
("grant_type", "client_credentials")
I am using v1 of the Azure REST API as you can see. I can't understand what's happening.
Error I received.
{
"error": {
"code": "BadRequest",
"message": "Resource not found for the segment 'messages?$filter=From'.",
"innerError": {
"request-id": "d2e9b359-d40d-4c0a-a0a5-b4e3cf4b5ecd",
"date": "2017-03-24T14:35:28"
}
}
}
I appericiate your help.
You can't encode the ? as %3F, that breaks it. By encoding it that way, you're indicating that it's not the query parameter separator, so the server is trying to find a segment called messages?$filter=From :).
I think the issue here is capitalization issues.
If you look at the query you have copied above, you have capitalized:
https://graph.microsoft.com/v1.0/me/MailFolders/INBOX/messages?$filter=From/EmailAddress/Address eq 'alerts-noreply#mail.windowsazure.com'
When these 3 letters need to all be lowercase (sample below from our documents):
GET https://graph.microsoft.com/v1.0/me/messages?$filter=from/emailAddress/address eq 'jon#contoso.com'