SwaggerRequestExample not showing on Swagger UI - c#

I've been trying to document this API with Swashbuckle.Example. I have followed this tutorial. The SwaggerResponseExample attribute works but the SwaggerRequestExample is not rendered. I have also checked the json in /docs/v1 and again the Response Example was present but not the Request Example.
What's going on?
How do I debug this?
Controller.cs
[System.Web.Http.HttpPost]
[SwaggerOperation(OperationId = "SAAT_ciclotarifario_GetById")]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(GrupoFtFronteiraPostResponseExamples))]
[SwaggerRequestExample(typeof(GrupoFtFronteira), typeof(GrupoFtFronteiraPostRequestExamples))]
[ActionName("GrupoFtFronteira")]
public HttpResponseMessage PostGrupoFtFronteira([FromBody] GrupoFtFronteira requestBody)
{
try
{
var novoGrupoFtFronteira = _grupofronteiraNegocio.PostGrupoFtFronteira(requestBody);
return new HttpResponseMessage(HttpStatusCode.OK)
{
ReasonPhrase = "Success",
Content = new StringContent("idGrupoFtFronteira: " + novoGrupoFtFronteira.IdGrupoftfronteira, System.Text.Encoding.UTF8, "application/json")
};
}
catch (Exception e)
{
var msg = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
ReasonPhrase = "Internal Server Error",
Content = new StringContent("Erro ao tentar atualizar dados. " + e.Message, System.Text.Encoding.UTF8, "application/json")
};
throw new HttpResponseException(msg);
}
}
SwaggerConfig.cs
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.DocumentFilter<SwaggerFilterOutControllers>();
c.SingleApiVersion("v1", "ons.sact.servico.controller");
c.ApiKey("Authorization")
.Description("JWT token")
.Name("Bearer")
.In("header");
c.OperationFilter<ExamplesOperationFilter>();
})
.EnableSwaggerUi(c =>
{
c.EnableApiKeySupport("apiKey", "header");
});
}
}

I found it!
It seems that I was supposed to fill the Request Type inside this foreach ( KeyValuePair<string, Swashbuckle.Swagger.Schema> definition in schemaRegistry.Definitions)
There's probably a better way around this, since this is a hardcoded solution...
Anyway, thanks a lot for those who helped me out!
SwaggerFilterOutControllers.cs
void IDocumentFilter.Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
var models = new List<string>();
foreach (ApiDescription apiDescription in apiExplorer.ApiDescriptions)
{
Console.WriteLine(apiDescription.Route.RouteTemplate);
if (apiDescription.RelativePathSansQueryString().StartsWith("api/v1/")
&& !(apiDescription.RelativePathSansQueryString().StartsWith("api/v1/SAAT/ciclotarifario")
|| apiDescription.RelativePathSansQueryString().StartsWith("api/v1/SAAT/GrupoFtFronteira")))
{
string path = "/" + apiDescription.RelativePathSansQueryString();
swaggerDoc.paths.Remove(path);
}
}
foreach ( KeyValuePair<string, Swashbuckle.Swagger.Schema> definition in schemaRegistry.Definitions)
{
Console.WriteLine(definition.Key);
if (!(definition.Key.Equals("CiclotarifarioPostRequest") ||
definition.Key.Equals("GrupoFtFronteira")
))
{
models.Add(definition.Key.ToString());
}
}
foreach (string modelo in models)
{
swaggerDoc.definitions.Remove(modelo);
}
CreateHeadersDict();
var allOtherPaths = swaggerDoc.paths
.Select(entry => entry.Value)
.ToList();
foreach (var path in allOtherPaths)
{
AddHeadersToPath(path, HeaderType.TokenAuth);
}
}

Related

How to use OdataNextLink in Microsoft Graph API Beta 5

I was using the Microsoft Graph API 1.0 but have updated to the Beta in order to use CustomSecurityAttributeValue support.
I've managed to port most of the code but I can't see any way to process multiple results pages.
Previously you would just do something like
if (membersPage.NextPageRequest != null)
membersPage = await membersPage.NextPageRequest.GetAsync();
But NextPageRequest no longer exists, the only available information is OdataNextLink which is a string with no obvious way to request the next page or create a raw request using the url.
Code I have so far:
public async Task<IEnumerable<Microsoft.Graph.Beta.Models.User>> GetGraphUsersInGroups(IEnumerable<string> groupIds, string? searchText = null)
{
Dictionary<String, Microsoft.Graph.Beta.Models.User> users = new Dictionary<String, Microsoft.Graph.Beta.Models.User>();
foreach (var groupId in groupIds)
{
try
{
var membersPage = await GraphClient.Groups[groupId].Members
.GetAsync((memberRequest) => {
memberRequest.Headers.Add(new KeyValuePair<string, string>("$count", "true"));
memberRequest.Headers.Add(new KeyValuePair<string, string>("ConsistencyLevel", "eventual"));
memberRequest.QueryParameters.Count = true;
memberRequest.QueryParameters.Orderby = new[] { "displayName" };
if (searchText != null)
memberRequest.QueryParameters.Search = $"\"displayName:{searchText}\"";
});
while (membersPage != null)
{
foreach (var member in membersPage.Value.OfType<Microsoft.Graph.Beta.Models.User>())
{
users[member.Id] = member;
}
if (membersPage.OdataNextLink != null)
{
// How to use membersPage.OdataNextLink???
}
else
break;
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
}
return users.Values;
}
You should use the PageIterator, see an example below:
var users = new List<User>();
var userResponse = await serviceClient.Users.GetAsync((builder) => {
// builder.SomeStuff
});
// Added the namespace here, just for some clarity :-)
var pageIterator = Microsoft.Graph.PageIterator<User,UserCollectionResponse>
.CreatePageIterator(serviceClient, userResponse, (user) =>
{
users.Add(user.Id);
return true; });

'Unable to deserialize content' when updating Microsoft calendar events in batch on retry

I'm working on an application based on .NET Framework 4.8. I'm using Microsoft Batching API. The below are code snippets
public async Task<List<BatchResponse>> UpdateEventsInBatchAsync(string accessToken, Dictionary<int, Tuple<string, OfficeEvent>> absEvents)
{
var httpMethod = new HttpMethod("PATCH");
var batches = GetUpdateRequestBatches(absEvents, httpMethod);
var graphClient = GetGraphClient(accessToken);
var batchResponses = new List<BatchResponse>();
foreach (var batch in batches)
{
try
{
var batchResponseList = await ExecuteBatchRequestAsync(graphClient, batch).ConfigureAwait(false);
batchResponses.AddRange(batchResponseList);
}
catch (ClientException exc)
{
_logService.LogException("Error while processing update batch", exc);
batchResponses.Add(new BatchResponse
{ StatusCode = HttpStatusCode.InternalServerError, ReasonPhrase = exc.Message });
}
catch (Exception exc)
{
_logService.LogException("Error while processing update batch", exc);
batchResponses.Add(new BatchResponse { StatusCode = HttpStatusCode.InternalServerError, ReasonPhrase = exc.Message });
}
}
return batchResponses;
}
The respective methods used in the above code are mentioned below in respective order-
GetUpdateRequestBatches
private IEnumerable<BatchRequestContent> GetUpdateRequestBatches(Dictionary<int, Tuple<string, OfficeEvent>> absEvents, HttpMethod httpMethod)
{
var batches = new List<BatchRequestContent>();
var batchRequestContent = new BatchRequestContent();
const int maxNoBatchItems = 20;
var batchItemsCount = 0;
foreach (var kvp in absEvents)
{
System.Diagnostics.Debug.Write($"{kvp.Key} --- ");
System.Diagnostics.Debug.WriteLine(_serializer.SerializeObject(kvp.Value.Item2));
var requestUri = $"{_msOfficeBaseApiUrl}/me/events/{kvp.Value.Item1}";
var httpRequestMessage = new HttpRequestMessage(httpMethod, requestUri)
{
Content = _serializer.SerializeAsJsonContent(kvp.Value.Item2)
};
var requestStep = new BatchRequestStep(kvp.Key.ToString(), httpRequestMessage);
batchRequestContent.AddBatchRequestStep(requestStep);
batchItemsCount++;
// Max number of 20 request per batch. So we need to send out multiple batches.
if (batchItemsCount > 0 && batchItemsCount % maxNoBatchItems == 0)
{
batches.Add(batchRequestContent);
batchRequestContent = new BatchRequestContent();
batchItemsCount = 0;
}
}
if (batchRequestContent.BatchRequestSteps.Count < maxNoBatchItems)
{
batches.Add(batchRequestContent);
}
if (batches.Count == 0)
{
batches.Add(batchRequestContent);
}
return batches;
}
GetGraphClient
private static GraphServiceClient GetGraphClient(string accessToken)
{
var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(requestMessage =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
return graphClient;
}
ExecuteBatchRequestAsync
private async Task<List<BatchResponse>> ExecuteBatchRequestAsync(IBaseClient graphClient, BatchRequestContent batch)
{
BatchResponseContent response = await graphClient.Batch.Request().PostAsync(batch);
Dictionary<string, HttpResponseMessage> responses = await response.GetResponsesAsync();
var batchResponses = new List<BatchResponse>();
var failedReqKeys = new Dictionary<string, TimeSpan>();
foreach (var key in responses.Keys)
{
using (HttpResponseMessage httpResponseMsg = await response.GetResponseByIdAsync(key))
{
var responseContent = await httpResponseMsg.Content.ReadAsStringAsync();
string eventId = null;
var reasonPhrase = httpResponseMsg.ReasonPhrase;
if (!string.IsNullOrWhiteSpace(responseContent))
{
var eventResponse = JObject.Parse(responseContent);
eventId = (string)eventResponse["id"];
// If still null, then might error have occurred
if (eventId == null)
{
var errorResponse = _serializer.DeserializeObject<ErrorResponse>(responseContent);
var error = errorResponse?.Error;
if (error != null)
{
if (httpResponseMsg.StatusCode == (HttpStatusCode)429)
{
System.Diagnostics.Debug.WriteLine($"{httpResponseMsg.StatusCode} {httpResponseMsg.Content}");
var executionDelay = httpResponseMsg.Headers.RetryAfter.Delta ?? TimeSpan.FromSeconds(5);
failedReqKeys.Add(key, executionDelay);
continue;
}
reasonPhrase = $"{error.Code} - {error.Message}";
}
}
}
var batchResponse = new BatchResponse
{
Key = key,
EventId = eventId,
StatusCode = httpResponseMsg.StatusCode,
ReasonPhrase = reasonPhrase
};
batchResponses.Add(batchResponse);
}
}
if (failedReqKeys.Count == 0) return batchResponses;
return await HandleFailedRequestsAsync(graphClient, failedReqKeys, batch, batchResponses).ConfigureAwait(false);
}
HandleFailedRequestsAsync
private async Task<List<BatchResponse>> HandleFailedRequestsAsync(IBaseClient graphClient, Dictionary<string, TimeSpan> failedReqKeys, BatchRequestContent batch, List<BatchResponse> batchResponses)
{
// Sleep for the duration as suggested in RetryAfter
var sleepDuration = failedReqKeys.Values.Max();
Thread.Sleep(sleepDuration);
var failedBatchRequests = batch.BatchRequestSteps.Where(b => failedReqKeys.Keys.Contains(b.Key)).ToList();
var failedBatch = new BatchRequestContent();
foreach (var kvp in failedBatchRequests)
{
failedBatch.AddBatchRequestStep(kvp.Value);
}
var failedBatchResponses = await ExecuteBatchRequestAsync(graphClient, failedBatch);
batchResponses.AddRange(failedBatchResponses);
return batchResponses;
}
I'm getting an error as on the first line in method ExecuteBatchRequestAsync as
Microsoft.Graph.ClientException: Code: invalidRequest
Message: Unable to deserialize content.
---> System.ObjectDisposedException: Cannot access a closed Stream.
Can anyone nudge me where I'm doing wrong?

Elasticsearch NEST update by script

I'm trying to update some fields by script. In Postman I'm sending the following request:
https://search.test.com/items_example/item/en_01_2/_update
the request's body:
{
"script": {
"inline": "ctx._source.Title = params.Title; ctx._source.Desc = params.Desc",
"params": {
"Title": "New Title",
"Desc": "New Desc"
}
}
}
But I have no idea how I can send this request using NEST. Can someone please help me?
Elasticsearch 5.4.1 version, NEST 5.6.1
Update it with your index settings & query
var elasticClient = new ElasticClient(settings);
var scriptParams = new Dictionary<string, object>
{
{"Title", "New Title"},
{"Desc", "New Desc"}
};
var response = elasticClient
.UpdateByQuery<dynamic>(q => q.Query(rq => rq.Term(....))
.Script(script =>
script.Inline(
$"ctx._source.Title = params.Title;" +
$"ctx._source.Desc = params.Desc ;"
)
.Params(scriptParams));
Edit: If you're looking for just Update, just change the syntax to
var response = elasticClient.Update<dynamic>(
"items_example/item/en_01_2" //This is your document path
, request => request.Script(
script =>
script.Inline(
$"ctx._source.Title = params.Title;" +
$"ctx._source.Desc = params.Desc ;"
)
.Params(scriptParams)));
If someone is here looking for the solution with NEST 6x version, please see below
public async Task UpdateId(MetaData request)
{
try
{
var scriptParams = new Dictionary<string, object>
{
{ "Id",request.Id}
};
var script = $"ctx._source.Id= params.Id;";
var indexResponse =
await EsClient.UpdateByQueryAsync<Doc>(
qd => qd.Index(request.IndexName)
.Conflicts(Conflicts.Proceed)
.Query(
rq => rq.Term("_id", request._id))
.Script(
s => s.Source(script)
.Params(scriptParams)
)
)
);
if (!indexResponse.IsValid)
{
}
}
catch (Exception ex)
{
}
}
[ElasticsearchType(Name = "_doc")]
public class Doc
{
}

Google Analytics API BatchRequest - Filtering/Callback

I am trying to submit a BatchRequest and unsure of how to apply a filter or how to handle the callback.
class Program
{
static void Main(string[] args)
{
try
{
new Program().Run().Wait();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
Console.Read();
}
private async Task Run()
{
var privatekey = "private key";
var accountEmailAddress = "email address";
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(accountEmailAddress) {
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromPrivateKey(privatekey));
var service = new AnalyticsService(new BaseClientService.Initializer() {
HttpClientInitializer = credentials,
ApplicationName = "Test"
});
var request = new BatchRequest(service);
request.Queue<DataResource.GaResource.GetRequest>(service.Data.Ga.Get("ga:XXXXXX", "30daysAgo", "yesterday", "ga:sessions"),
(content, error, i, message) =>
{
//callback code
});
await request.ExecuteAsync();
}
}
Two Questions:
How do I apply the following filter to the request?
ga:pagePath==/page1.html
How do I handle the callback and view the data returned?
Update
I believe I have solved the filter issue with the following code:
var req = service.Data.Ga.Get("ga:XXXXXX", "30daysAgo", "yesterday", "ga:sessions");
req.Filters = "ga:pagePath==/page1.html";
request.Queue<DataResource.GaResource.GetRequest>(req,
(content, error, i, message) =>
{
//callback code
});
I am still unsure of how to handle the callback. The "content" parameter is returned as the class: Google.Apis.Analytics.v3.DataResource.GaResource.GetRequest
I was finally able to get this working.
For future reference for anyone, here is a working .Net example of submitting a BatchRequest to the Analytics API.
class Program
{
static void Main(string[] args)
{
try
{
new Program().Run().Wait();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
Console.Read();
}
private async Task Run()
{
var privatekey = "private key";
var accountEmailAddress = "email address";
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(accountEmailAddress)
{
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromPrivateKey(privatekey));
var service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "Test"
});
var request = new BatchRequest(service);
BatchRequest.OnResponse<GaData> callback = (content, error, i, message) =>
{
if (error != null)
{
Console.WriteLine("Error: {0}", error.Message);
}
else
{
if (content.Rows != null)
{
foreach (var item in content.Rows)
{
foreach (var item1 in item)
{
Console.WriteLine(item1);
}
}
}
else
{
Console.WriteLine("Not Found");
}
}
};
int counter = 0;
while (counter < 5)
{
var req = service.Data.Ga.Get("ga:XXXXX", "30daysAgo", "yesterday", "ga:sessions");
req.Filters = "ga:pagePath==/page" + counter + ".html";
request.Queue<GaData>(req, callback);
counter++;
}
await request.ExecuteAsync();
}
}
So I am not familiar with C# but I did look at the source code for the get method. And the GetRequest object has a filters property which you can set.
var request = service.Data.Ga.Get("ga:XXXXXX",
"30daysAgo", "yesterday", "ga:sessions");
request.dimensions("ga:pagePath");
request.filters("ga:pagePath==/page1.html");
I would encourage you to also look at the Core Reporting API reference for the full set of parameters.
I would imagine that the content object in the callback method would correspond to the GaData response object
I hope that helps some.

How to create async http requests in a loop?

Yesterday I've found out how to create several async http requests without async/await. But today I need to do it in a loop: if some of responses don't satisfy some condition - I need to change a request for them and send these requests again. It may be repeated several times.
I've tried this code:
do
{
var loadingCoordinatesTasks = new List<Task<Terminal>>();
var totalCountOfTerminals = terminalPresetNode.ChildNodes.Count;
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
foreach (var terminal in terminals.Except(_terminalsWithCoordinates))
{
var address = terminal.GetNextAddress();
var webRequest = (HttpWebRequest)WebRequest.Create(GeoCoder.GeoCodeUrl + address);
var webRequestTask = Task.Factory.FromAsync<WebResponse>(webRequest.BeginGetResponse,
webRequest.EndGetResponse,
terminal);
var parsingTask = webRequestTask.ContinueWith(antecedent =>
{
// Parse the response
});
loadingCoordinatesTasks.Add(parsingTask);
}
Task.Factory.ContinueWhenAll(loadingCoordinatesTasks.ToArray(), antecedents =>
{
foreach (var antecedent in antecedents)
{
var terminalWithCoordinates = antecedent.Result;
if (antecedent.Status == TaskStatus.RanToCompletion &&
!terminalWithCoordinates.Coordinates.AreUnknown)
{
_terminalsWithCoordinates.Add(terminalWithCoordinates);
_countOfProcessedTerminals++;
}
}
});
} while (_countOfProcessedTerminals < totalCountOfTerminals);
but is it possible to check the condition in while just after every single set of requests executed?
You can perform the check after increasing the count:
_countOfProcessedTerminals++;
if (_countOfProcessedTerminals >= totalCountOfTerminals)
{
break;
}
Is _countOfProcessedTerminals thread-safe though?
I manage to do it using recursion:
public void RunAgainFailedTasks(IEnumerable<Task<Terminal>> tasks)
{
Task.Factory.ContinueWhenAll(tasks.ToArray(), antecedents =>
{
var failedTasks = new List<Task<Terminal>>();
foreach (var antecedent in antecedents)
{
var terminal = antecedent.Result;
// Previous request was failed
if (terminal.Coordinates.AreUnknown)
{
string address;
try
{
address = terminal.GetNextAddress();
}
catch (FormatException) // No versions more
{
continue;
}
var getCoordinatesTask = CreateGetCoordinatesTask(terminal, address);
failedTasks.Add(getCoordinatesTask);
}
else
{
_terminalsWithCoordinates.Add(terminal);
}
}
if (failedTasks.Any())
{
RunAgainFailedTasks(failedTasks);
}
else
{
// Display a map
}
}, CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
private Task<Terminal> CreateGetCoordinatesTask(Terminal terminal, string address)
{
var webRequest = (HttpWebRequest)WebRequest.Create(GeoCoder.GeoCodeUrl + address);
webRequest.KeepAlive = false;
webRequest.ProtocolVersion = HttpVersion.Version10;
var webRequestTask = Task.Factory.FromAsync<WebResponse>(webRequest.BeginGetResponse,
webRequest.EndGetResponse,
terminal);
var parsingTask = webRequestTask.ContinueWith(webReqTask =>
{
// Parse the response
});
return parsingTask;
}

Categories

Resources