WebAPI HttpContent convert to typed object - c#

I am working on custom filter which should accomplish simple thing. All my APIs wrapped into 'Response' object. I want to fill in all properties using filter. This is code I have for the filter:
public class MeteringFilter : IActionFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
var attribute =
actionContext.ActionDescriptor.GetCustomAttributes<MeterAttribute>(true).SingleOrDefault() ??
actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<MeterAttribute>(true).SingleOrDefault();
if (attribute == null) return continuation();
var operation = actionContext.ActionDescriptor.ActionName;
var user = actionContext.RequestContext.Principal.Identity.Name;
var started = DateTimeOffset.Now;
return continuation().ContinueWith(t =>
{
var completed = DateTimeOffset.Now;
var duration = completed - started;
var c = t.Result.Content;
// This is code which does not work but I like to have:
// When debugger stops here I can see Content.Value and my object but I can't use this property like below
var cv = t.Result.Content.Value as Response<object>;
return t.Result;
});
}
public bool AllowMultiple => true;
}
I found similar question where it was suggested to do var c = t.Result.Content.ReadAsAsync(typeof(Response<>)); but I can't do this because I can't make lambda function async in this case.
Any suggestion on how to get typed object out of HttpContent so I can assign properties before it returns to caller?
Here is Response<T>
public class Response<T>
{
public string Url { get; set; }
public DateTime ServerTime { get; set; }
public TimeSpan TimeTook { get; set; }
public T Data { get; set; }
public Error Error { get; set; }
}
EDIT
Here is how code looks now. I do get access to object, but webservice does not respond with data I fill to client. It seems that code executed later after serialization/media formatting takes place.
I guess question becomes how do I add generic "handler" before web service returns but with access to beginning of call (so I can measure times, see request params, etc)
return continuation().ContinueWith(t =>
{
var c = t.Result.Content.ReadAsAsync(typeof(Response<object>), cancellationToken);
if (c.Result is Response<object> response)
{
Debug.WriteLine("Adding times");
response.ServerTime = startedOn;
response.TimeTook = DateTime.Now - startedOn;
}
return t.Result;
}, cancellationToken);
EDIT 2:
Here is sample web api method which I want to intercept:
[HttpGet]
public Response<LookupResponseData> Carrier(int? key = null, string id = "")
{
return this.GetKeyIdBundleForLookup("Carriers", key, id);
}
private Response<LookupResponseData> GetKeyIdBundleForLookup(string lookupId, int? key, string id)
{
if (!key.HasValue && string.IsNullOrEmpty(id))
return new Response<LookupResponseData>
{
Error = new Error { Code = ErrorCodes.InvalidQueryParameter, Message = "Either key or id must be specified" }
};
var r = new Response<LookupResponseData>();
try
{
this.LookupService.GetKeyIdDescription(this.AccountId, lookupId, key, id, out var keyResult, out var idResult, out var description);
if (!keyResult.HasValue)
return new Response<LookupResponseData>
{
Error = new Error { Code = ErrorCodes.InvalidOrMissingRecord, Message = "No record found for parameters specified" }
};
r.Data = new LookupResponseData { Key = keyResult.Value, Id = idResult, Description = description };
}
catch (Exception ex)
{
this.LoggerService.Log(this.AccountId, ex);
return new Response<LookupResponseData>
{
Error = new Error { Code = ErrorCodes.Unknown, Message = "API Call failed, please contact support. Details logged." }
};
}
return r;
}

All my APIs wrapped into 'Response' object.
First you can simplify your results by creating a implicit operators:
public class Response
{
public string Url { get; set; }
public DateTime ServerTime { get; set; }
public TimeSpan TimeTook { get; set; }
}
public class Response<T> : Response
{
public T Data { get; set; }
public Error Error { get; set; }
public static implicit operator Response<TData>(TData data)
{
var result = new Response<TData>
{
Data = data,
};
return result;
}
public static implicit operator Response<TData>(Error error)
{
var result = new Response<TData>
{
Error = error,
};
return result;
}
}
Now it should be easier to really ignore the repeated code of creating the response:
private Response<LookupResponseData> GetKeyIdBundleForLookup(
string lookupId, int? key, string id)
{
if (!key.HasValue && string.IsNullOrEmpty(id))
return new Error
{
Code = ErrorCodes.InvalidQueryParameter,
Message = "Either key or id must be specified"
};
try
{
this.LookupService.GetKeyIdDescription(this.AccountId,
lookupId,
key,
id,
out var keyResult,
out var idResult,
out var description);
if (!keyResult.HasValue)
return new Error
{
Code = ErrorCodes.InvalidOrMissingRecord,
Message = "No record found for parameters specified"
};
return new LookupResponseData
{
Key = keyResult.Value,
Id = idResult, Description = description
};
catch (Exception ex)
{
this.LoggerService.Log(this.AccountId, ex);
return new Error
{
Code = ErrorCodes.Unknown,
Message = "API Call failed, please contact support. Details logged." }
};
}
}
Then you can create an Core Async Action Filter:
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
var started = DateTimeOffset.Now;
// Action Executes
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
if (result.Context.Result is Response response)
{
response.ServerTime = started;
response.TimeTook = DateTimeOffset.Now - started;
}
}
}
Or Non-Core (MVC):
public class SampleActionFilter : ActionFilterAttribute
{
private const string TimerKey = nameof(SampleActionFilter ) + "_TimerKey";
public override void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items[TimerKey] = DateTimeOffset.Now;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is Response response)
&& context.HttpContext.Items[TimerKey] is DateTimeOffset started)
{
response.ServerTime = started;
response.TimeTook = DateTimeOffset.Now - started;
}
}
Or Non-Core (WebApi):
public class SampleActionFilter : ActionFilterAttribute
{
private const string TimerKey = nameof(SampleActionFilter ) + "_TimerKey";
public override void OnActionExecuting(HttpActionContext context)
{
context.Request.Properties[TimerKey] = DateTimeOffset.Now;
}
public override void OnActionExecuted(HttpActionExecutedContext context)
{
if (context.Result is Response response)
&& context.Request.Properties[TimerKey] is DateTimeOffset started)
{
response.ServerTime = started;
response.TimeTook = DateTimeOffset.Now - started;
}
}

I tweaked your code. I hope it helps.
I couldn't check syntax errors though
return await continuation().ContinueWith(async t =>
{
var result = await t;
var c = await result.Content.ReadAsAsync(typeof(Response<object>), cancellationToken);
if (c is Response<object> response)
{
Debug.WriteLine("Adding times");
response.ServerTime = startedOn;
response.TimeTook = DateTime.Now - startedOn;
}
return result;
}, cancellationToken);

Related

Mongo time-series inserts using c# produce exception after short time

I want to test if mongo db can have a collection up to 50 000 000 000.
So I insert 10K elements every second using method:
public async Task InsertManyAsync(List<DBRoutLine> list)
{
await _collRouts.InsertManyAsync(list);
}
Data looks like this:
namespace DbLayer.Models
{
public class DBRoutLineMeta
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string id { get; set; }
public int counter { get; set; }
}
[BsonIgnoreExtraElements]
public class DBRoutLine
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string id { get; set; }
public DBRoutLineMeta meta { get; set; } = new DBRoutLineMeta();
public DateTime timestamp { get; set; } = DateTime.UtcNow;
public string some_data { get; set; } = DateTime.Now.ToString();
}
}
id members not required actually but I have them, just for testing.
So I've got exception like this:
"A bulk write operation resulted in one or more errors. WriteErrors: [ { Category : "DuplicateKey", Code : 11000, Message : "E11000 duplicate key error collection: TSTest.system.buckets.TSTable dup key: { _id: ObjectId('634e87301297fa65b7df9923') }" } ]."
after sometime. It can be also like this:
"time-series insert failed: TSTest.TSTable :: caused by :: Expected
And it will never recover from error even if I recreate connection to mongo server. Only application restart helps to insert records again.
Test code:
using DbLayer.Models;
using DbLayer.Services;
using MongoDB.Bson;
Console.WriteLine("Hello, World!");
var service = new RoutService();
try
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
var list = new List<DBRoutLine>();
for (int i = 0; i < 10000; i++)
{
DBRoutLine line = new DBRoutLine();
list.Add(line);
}
Task task = Task.Run(async () => {
int max_counter = await service.GetMaxCount();
bool recover = false;
while (!token.IsCancellationRequested)
{
try
{
if (!recover)
{
foreach (DBRoutLine line in list)
{
line.meta.counter = ++max_counter;
line.id = ObjectId.GenerateNewId().ToString();
line.meta.id = line.id;
}
}
var t1 = DateTime.Now;
await service.InsertManyAsync(list);
var t2 = DateTime.Now;
max_counter = await service.GetMaxCount();
var t3 = DateTime.Now;
Console
.WriteLine(
$"{max_counter}->Insert:{(int)(t2 - t1).TotalMilliseconds}, GetMax:{(int)(t3 - t2).TotalMilliseconds}");
recover = false;
}
catch(Exception ex)
{
recover = true;
await Task.Delay(3000);
Console.WriteLine(ex.Message.ToString());
service = new RoutService();
max_counter = await service.GetMaxCount();
}
}
}, token);
Console.WriteLine("Press any key to stop emulation\n");
Console.ReadKey();
tokenSource.Cancel();
Task.WaitAll(task);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Service code:
using DbLayer.Models;
using MongoDB.Bson;
using MongoDB.Driver;
namespace DbLayer.Services
{
public class RoutService:IDisposable
{
private readonly IMongoCollection<DBRoutLine> _collRouts;
private readonly MongoClient _mongoClient;
private readonly string CollName = "TSTable";
public RoutService(
)
{
var ConnectionString = "mongodb://mongoservice:27017";
_mongoClient = new MongoClient(
ConnectionString);
var mongoDatabase = _mongoClient.GetDatabase(
"TSTest");
var filter = new BsonDocument("name", CollName);
var options = new ListCollectionNamesOptions { Filter = filter };
if (!mongoDatabase.ListCollectionNames(options).Any())
{
var createOptions = new CreateCollectionOptions();
var timeField = nameof(DBRoutLine.timestamp);
var metaField = nameof(DBRoutLine.meta);
createOptions.TimeSeriesOptions =
new TimeSeriesOptions(timeField, metaField, TimeSeriesGranularity.Minutes);
mongoDatabase.CreateCollection(
CollName,
createOptions);
}
_collRouts =
mongoDatabase.GetCollection<DBRoutLine>(
CollName
);
CreateIndexes();
}
private void CreateIndexes()
{
{
IndexKeysDefinition<DBRoutLine> keys =
new IndexKeysDefinitionBuilder<DBRoutLine>()
.Descending(d => d.meta.counter);
var indexModel = new CreateIndexModel<DBRoutLine>(
keys, new CreateIndexOptions()
{ Name = "counter" }
);
_collRouts.Indexes.CreateOneAsync(indexModel);
}
////////////////////////////////////////////////
{
IndexKeysDefinition<DBRoutLine> keys =
new IndexKeysDefinitionBuilder<DBRoutLine>()
.Ascending(d => d.meta.id);
var indexModel = new CreateIndexModel<DBRoutLine>(
keys, new CreateIndexOptions()
{ Name = "id" }
);
_collRouts.Indexes.CreateOneAsync(indexModel);
}
}
public async Task InsertManyAsync(List<DBRoutLine> list)
{
await _collRouts.InsertManyAsync(list);
}
public async Task<int> GetMaxCount()
{
var last = await _collRouts
.Find(i=> i.meta.counter > 0)
.SortByDescending( i => i.meta.counter).FirstOrDefaultAsync();
if (last == null)
{
return 0;
}
return last.meta.counter;
}
public void Dispose()
{
}
}
}
project repository:
github.com/pruginkad/TestMongo
Ok, I found the bug. I changed timestamp only once when I created List of documents.
in this code:
foreach (DBRoutLine line in list)
{
line.meta.counter = ++max_counter;
line.id = ObjectId.GenerateNewId().ToString();
line.meta.id = line.id;
line.timestamp = DateTime.UtcNow;//missing line
}
I had to update timestamp, my mistake.
Anyway it's kind of strange that exception happen after every 17M documents and disappear after restart of mongo db

I am getting Error 415 while uploading image in Angular and .Net core project

I want to upload an image to my project, but unfortunately it gives an error of 415. Swagger does not give an error when I install it from above, but it gives an error when I install it from above Angular. What is the solution to this?
Backend Web Api Code;
[Produces("application/json", "text/plain")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(string))]
[HttpPost("update")]
public async Task<IActionResult> Update([FromFrom] UpdatePresidentMessageCommand updatePresidentMessage)
{
var result = await Mediator.Send(updatePresidentMessage);
if (result.Success)
{
return Ok(result.Message);
}
return BadRequest(result.Message);
}
}
Backend Handlers Code;
if (request.Image != null)
{
var fileUpload = Core.Extensions.FileExtension.FileUpload(request.Image, fileType: Core.Enums.FileType.Image);
if (fileUpload != null)
{
isTherePresidentMessageRecord.Image = fileUpload.Path;
}
}
Angular Service Code;
updatePresidentMessage(presidentMessage: FormData): Observable<any> {
return this.httpClient.post('https://localhost:60021/Admin/api' + '/presidentMessages/update', presidentMessage,{ responseType: 'text' });
}
Angular Ts Code;
update(): void {
let presidentModel = Object.assign({}, this.presidentMessageFormGroup.value);
const formData = new FormData();
formData.append('id', presidentModel.id.toString());
formData.append('shortDescription', presidentModel.shortDescription);
formData.append('description', presidentModel.description);
for (const photo of this.photos) {
formData.append('image', photo, photo.name);
}
this.presidentMessageService.updatePresidentMessage(formData).subscribe(response => {
this.alertifyService.success(response);
});
}
onFileSelect(event: any) {
if (event.target.files.length > 0) {
for (const file of event.target.files) {
this.photos.push(file);
}
}
}
" const headers = new HttpHeaders().set('Content-Type', 'multipart/form-data');
" the error I get when I add;
enter image description here
UpdatePresidentMessageCommand Class;
public int ID { get; set; }
public string ShortDescription { get; set; }
public string Description { get; set; }
public IFormFile Image { get; set; }
public class UpdatePresidentMessageCommandHandler : IRequestHandler<UpdatePresidentMessageCommand, IResult>
{
private readonly IPresidentMessageRepository presidentMessageRepository;
private readonly IUserService userService;
public UpdatePresidentMessageCommandHandler(IUserService userService, IPresidentMessageRepository presidentMessageRepository)
{
this.userService = userService;
this.presidentMessageRepository = presidentMessageRepository;
}
[LogAspect(typeof(FileLogger))]
[SecuredOperation(Priority = 1)]
public async Task<IResult> Handle(UpdatePresidentMessageCommand request, CancellationToken cancellationToken)
{
var isTherePresidentMessageRecord = await this.presidentMessageRepository.GetAsync(u => u.ID == request.ID);
if (isTherePresidentMessageRecord == null)
{
return new ErrorResult(message: Messages.Error);
}
isTherePresidentMessageRecord.ShortDescription = request.ShortDescription;
isTherePresidentMessageRecord.Description = request.Description;
isTherePresidentMessageRecord.UpdateByUserID = this.userService.GetNameIdentifier();
isTherePresidentMessageRecord.UpdateDateTime = System.DateTime.Now;
if (request.Image != null)
{
var fileUpload = Core.Extensions.FileExtension.FileUpload(request.Image, fileType: Core.Enums.FileType.Image);
if (fileUpload != null)
{
isTherePresidentMessageRecord.Image = fileUpload.Path;
}
}
this.presidentMessageRepository.Update(isTherePresidentMessageRecord);
await this.presidentMessageRepository.SaveChangesAsync();
return new SuccessResult(Messages.Updated);
}
}
Try to set the correct 'Content-Type' on the header of your POST request.

Parse REST api response for different possible classes in C#

I'm working with a REST API, that returns 2 different kinds of XML responses for the same request.
For example if I ask for a ticket using some ticket number, say 12345 to this API, it either returns:
The ticket:
Or says that it doesn't have the ticket:
(I couldn't format my XML for some reason so just pasted the screenshot.)
Note that the status code comes to be Ok in both the cases. I'm aware that it's a bad api design but we can't change anything about it.
With some help from this JSON2Csharp website, I came up with these classes to represent the response:
The Ticket class:
[XmlRoot(ElementName = "Tickets")]
public class TicketsResponse
{
public List<Ticket> Tickets { get; set; } = new List<Ticket>();
public bool HasTickets() => Tickets.Any();
}
[XmlRoot(ElementName = "Ticket")]
public class Ticket
{
[XmlElement(ElementName = "Field1", IsNullable = true)]
public string Field1 { get; set; }
public bool ShouldSerializeField1() { return Field1 != null; }
[XmlElement(ElementName = "TicketNumber")]
public int TicketNumber { get; set; }
[XmlElement(ElementName = "SomeOtherDetails")]
public SomeOtherDetails SomeOtherDetails { get; set; }
[XmlElement(ElementName = "Accessorials")]
public object Accessorials { get; set; }
}
[XmlRoot(ElementName = "SomeOtherDetails")]
public class SomeOtherDetails
{
[XmlElement(ElementName = "SomeOtherField1", IsNullable = true)]
public string SomeOtherField1 { get; set; }
public bool ShouldSerializeSomeOtherField1() { return SomeOtherField1 != null; }
}
The Error class:
[XmlRoot(ElementName = "response")]
public class ErrorResponse
{
public byte requestId { get; set; }
public byte errorCode { get; set; }
public string errorDesc { get; set; }
public ErrorResponseBody body { get; set; }
public bool HasErrors()
{
var hasTopLevelError = errorCode != 0;
var hasErrorBody = body?.errors?.Any() ?? false;
if (hasTopLevelError || hasErrorBody)
{
return true;
}
return false;
}
public string ErrorMessage()
{
var hasTopLevelError = errorCode != 0;
var hasErrorBody = body?.errors?.Any() ?? false;
if (hasTopLevelError)
{
return errorDesc;
}
else if (hasErrorBody)
{
return string.Join(", ", body.errors.Select(e => e.errorDescription));
}
return null;
}
}
[XmlRoot(ElementName = "body")]
public class ErrorResponseBody
{
[XmlElement("errors")]
public List<Error> errors { get; set; }
}
[XmlRoot(ElementName = "Error")]
public class Error
{
public byte errorId { get; set; }
public string errorDescription { get; set; }
public string errorObjectId { get; set; }
}
I then call the API using a TicketNumber that exists.
I'm using RestSharp for calling the api:
public async void SendRequestAndReceiveResponse()
{
var restClient = new RestClient("https://someapiaddress.net");
var requestXMLBody = "<request><request_id>1</request_id><operation>retrieve</operation><method /><entity>ticket</entity><user>someuser</user><password>somepassword</password><body><ticket><TicketNumber>12345</TicketNumber></ticket></body></request>";
var request = new RestRequest("somexmlwebservice!process.action", Method.POST);
request.AddParameter("xmlRequest", requestXMLBody, "text/xml", ParameterType.QueryString);
var response = await restClient.ExecuteAsync<TicketsResponse>(request);
// Do other stuffs with this response...
}
Now this works very well. Because I know my response will have the ticket and that will correctly deserialize to TicketsResponse object.
But if I call the API using a TicketNumber that doesn't exist, I simply get TicketsResponse object that has an empty list of Tickets because this time I'm getting error response. The status code comes to be OK in this case too.
What I want to do here is that I want to capture the error message from the error response. (Response of either Ticket or Error applies to bunch of other processes as well, so it's important to grab this information in a single call.)
And if I knew this ticket doesn't exist, I could simply call the API this way and capture the errors. But that's not ideal nor even a good idea:
var response = await restClient.ExecuteAsync<ErrorResponse>(request);
So I thought of combining TicketsResponse and ErrorResponse, like this:
[XmlRoot]
public class CombinedResponse
{
[XmlElement(ElementName = "Tickets")]
public TicketsResponse Data { get; set; }
[XmlElement(ElementName = "response")]
public ErrorResponse NonData { get; set; }
}
And get the response using that class:
var response = await restClient.ExecuteAsync<CombinedResponse>(request);
The Status code comes OK (when it returns either data or error message) and I get my correct response in response.Content, but the deserialization doesn't work, so my response.Data will show 2 fields Data and NonData both as null. Ideally I should have gotten either my Ticket data or Error data in response.Data.
So my question is:
Is it possible to make this work using a single class for deserialization?
I have spent too much time on this so any help is appreciated.
Also please look at my model classes and suggest if there's better way of doing things.
This is how I solved this issue.
I'm posting here so others may find it helpful.
If there's a better way of doing this, please advise.
I created a method to call the API and deserialize the response to multiple types:
public async Task<(T1, T2)> SendRequestAndReceiveResponse<T1, T2>(RestRequest request)
{
// This can be done in the constructor so we don't instantiate new client for every request.
var restClient = new RestClient("https://someapiaddress.net");
// Get response:
var response = await restClient.ExecuteAsync(request);
// Log request and response here if you want.
if (response.ErrorException != null)
{
var message = $"An error occured during this request. Check request response entries for more details.";
var newException = new Exception(message, response.ErrorException);
throw newException;
}
else
{
var xmlDeserilizer = new RestSharp.Deserializers.XmlDeserializer();
var data = xmlDeserilizer.Deserialize<T1>(response);
var nonData = xmlDeserilizer.Deserialize<T2>(response);
return (data, nonData);
}
}
And used it, by sending the types I need:
public async Task<IEnumerable<Ticket>> FetchTickets()
{
var xmlRequestBody = "<request><request_id>1</request_id><operation>retrieve</operation><method /><entity>ticket</entity><user>someuser</user><password>somepassword</password><body><ticket><TicketNumber>12345</TicketNumber></ticket></body></request>";
var request = new RestRequest("somexmlwebservice!process.action", Method.POST);
request.AddParameter("xmlRequest", xmlRequestBody, "text/xml", ParameterType.QueryString);
var apiCallResult = await SendRequestAndReceiveResponse<TicketsResponse, ErrorResponse>(request);
if (apiCallResult.Item1 != null && apiCallResult.Item1.HasTickets())
{
// Do something with the tickets...
}
else if (apiCallResult.Item2 != null && apiCallResult.Item2.HasErrors())
{
// Do something with the errors...
}
// And so on...
}
My complete solution. If you just need the answer, please take a look at the accepted answer.
This is more like a documentation of the whole process for anyone who deals with RestSharp and XML.
Request:
To form a request body that looks like this, we need few classes like below:
[XmlRoot(ElementName = "request")]
[XmlInclude(typeof(RequestBodyWithTicketNumberOnly))] // To make sure 'body' can be serialized to RequestBodyWithTicketNumberOnly
public class TicketRequestBase
{
public byte request_id { get; set; }
public string operation { get; set; }
public string method { get; set; }
public string entity { get; set; }
public string user { get; set; }
public string password { get; set; }
// body can have different shapes, so not giving it any specific class name.
public object body { get; set; }
}
[XmlRoot(ElementName = "body")]
public class RequestBodyWithTicketNumberOnly
{
public TicketWithTicketNumberOnly ticket { get; set; }
}
[XmlRoot(ElementName = "ticket")]
public class TicketWithTicketNumberOnly
{
public string TicketNumber { get; set; }
}
A method to convert C# objects into XML strings, like so:
public static string ToXml<T>(T obj)
{
var settings = new XmlWriterSettings
{
Indent = false,
OmitXmlDeclaration = true,
NewLineHandling = NewLineHandling.None,
NewLineOnAttributes = false
};
var objType = obj.GetType();
var serializer = new XmlSerializer(objType);
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, settings))
{
serializer.Serialize(writer, obj, emptyNamespaces);
return stream.ToString();
}
}
A method to return request body as XML string:
public static string GetTicketFetchRequestBody(string ticketNumber)
{
if (ticketNumber== null) throw new ArgumentNullException(nameof(ticketNumber));
var singleTicketRequest = new TicketRequestBase()
{
request_id = 1,
operation = "retrieve",
method = string.Empty,
entity = "ticket",
user = "sauser",
password = "sapassword",
body = new RequestBodyWithTicketNumberOnly() { ticket = new TicketWithTicketNumberOnly { TicketNumber = ticketNumber} }
};
return ToXml(singleTicketRequest);
}
Response:
All the classes for the response are already documented in this question. Please take a look at them.
The top level method that gets the Ticket:
public static async Task<IEnumerable<Ticket>> FetchTickets()
{
var xmlRequestBody = GetTicketFetchRequestBody("12345");
var request = new RestRequest("somexmlwebservice!process.action", Method.POST);
request.AddParameter("xmlRequest", xmlRequestBody, "text/xml", ParameterType.QueryString);
var apiCallResult = await SendRequestAndReceiveResponse<TicketsResponse, ErrorResponse>(request);
if (apiCallResult.Item1 != null && apiCallResult.Item1.HasTickets())
{
// Do something with the tickets...
}
else if (apiCallResult.Item2 != null && apiCallResult.Item2.HasErrors())
{
// Do something with the errors...
}
// And so on...
}
The method that actually calls the API uses Proxy, logs requests and responses, and executes request using Polly retry policy:
public async Task<(T1, T2)> SendRequestAndReceiveResponse<T1, T2>(RestRequest request, bool useProxy = true)
{
// This can be done in the constructor so we don't instantiate new client for every request.
var restClient = new RestClient("https://someapiaddress.net");
if (useProxy) // This variable can even be initialized in the constructor of this RestClient
{
var proxy = GetWebProxy();
restClient.Proxy = proxy;
}
// Request Logging Part:
var requestAsJSONString = GetRequestForLogging(request, restClient);
// Log it using your logging provider.
// Response Part:
var response = await ExecuteAsyncWithPolicy(request, restClient);
// Response Logging Part:
var responseAsString = response.Content;
// Log it using your logging provider.
if (response.ErrorException != null)
{
var message = $"An error occured during this request. Check request response entries for more details.";
var newException = new Exception(message, response.ErrorException);
throw newException;
}
else
{
var xmlDeserilizer = new RestSharp.Deserializers.XmlDeserializer();
var data = xmlDeserilizer.Deserialize<T1>(response);
var nonData = xmlDeserilizer.Deserialize<T2>(response);
return (data, nonData);
}
}
Create Web proxy like so:
private static WebProxy GetWebProxy()
{
var proxyUrl = "http://proxy.companyname.com:9090/";
return new WebProxy()
{
Address = new Uri(proxyUrl),
BypassProxyOnLocal = false,
//UseDefaultCredentials = true, // This uses: Credentials = CredentialCache.DefaultCredentials
//*** These creds are given to the proxy server, not the web server ***
Credentials = CredentialCache.DefaultNetworkCredentials
//Credentials = new NetworkCredential("proxyUserName", "proxyPassword")
};
}
Create the request string with all the parameters like so:
private string GetRequestForLogging(IRestRequest request, IRestClient client)
{
var serializer = new JsonSerializer();
var requestToLog = new
{
// This will generate the actual Uri used in the request
RequestUri = client.BuildUri(request),
// Parameters are custom anonymous objects in order to have the parameter type as a nice string
// otherwise it will just show the enum value
parameters = request.Parameters.Select(parameter => new
{
name = parameter.Name,
value = parameter.Value,
type = parameter.Type.ToString()
}),
// ToString() here to have the method as a nice string otherwise it will just show the enum value
method = request.Method.ToString()
};
return serializer.Serialize(requestToLog);
}
Polly retry policy:
private AsyncPolicy<IRestResponse> GetRetryPolicy()
{
var policy = Polly.Policy.HandleResult<IRestResponse>((response) =>
{
return response.ResponseStatus != ResponseStatus.Completed;
})
//.Or<SomeKindOfCustomException>()
.RetryAsync();
return policy;
}
Call the API using the retry policy:
private async Task<IRestResponse> ExecuteAsyncWithPolicy(IRestRequest request, IRestClient restClient)
{
var policy = GetRetryPolicy();
var policyResult = await policy.ExecuteAndCaptureAsync(async () => await restClient.ExecuteAsync(request));
return (policyResult.Outcome == OutcomeType.Successful) ? policyResult.Result : new RestResponse
{
Request = request,
ErrorException = policyResult.FinalException
};
}
Hope this was helpful.

Xamarin.Forms Consume API

Can someone help me?
I'm try to consume api in my apps (I'm still learning). I can successfully call the api and get the data, but debug ended in DeserializeObject.
Can someone help me, and tell what must I do? or reference how to fix this?
This is my code:
My ViewModels
from ViewModels I call GetHeroes(), which calls my class in services.
public class DotaViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DotaViewModel()
{
GetHeroes(); // Start From Here
}
IDotaApi _rest = DependencyService.Get<IDotaApi>();
private ObservableCollection<Hero> heroes;
public ObservableCollection<Hero> Heroes
{
get { return heroes; }
set
{
heroes = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Heroes"));
}
}
public async void GetHeroes()
{
var result = await _rest.getheroes(); // Go Inside Here
if (result != null)
{
}
}
}
My Services
I got the data and stored it to var result, but my debug just when I DeserializeObject.
public class DotaApi : IDotaApi
{
string Base_url = "https://api.opendota.com/api/heroes";
public async Task<ObservableCollection<Hero>> getheroes()
{
string url = Base_url;
HttpClient client = new HttpClient();
HttpResponseMessage responseMessage = await client.GetAsync(url);
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await responseMessage.Content.ReadAsStringAsync(); // I Got Data Here
var json = JsonConvert.DeserializeObject<ObservableCollection<Hero>>(result); // But Stuck Here
return json;
}
return null;
}
}
This is My Model
public class Hero
{
public int id { get; set; }
public string name { get; set; }
public string localized_name { get; set; }
public string primary_attr { get; set; }
public string attack_type { get; set; }
public List<string> roles { get; set; }
public int legs { get; set; }
//[
//{"id":1,
//"name":"npc_dota_hero_antimage",
//"localized_name":"Anti-Mage",
//"primary_attr":"agi",
//"attack_type":"Melee",
//"roles":["Carry","Escape","Nuker"],
//"legs":2}
//}
//]
}
Try to implement the API calling method like below and add a debug point to the exception and check the issue
public async Task<ObservableCollection<Hero>> getheroes()
{
string Base_url = "https://api.opendota.com/api/heroes";
using (var httpClient = new HttpClient())
{
var data = new ObservableCollection<Hero>();
try
{
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var result = await httpClient.GetAsync(Base_url);
var response = await result.Content.ReadAsStringAsync();
data = JsonConvert.DeserializeObject<ObservableCollection<Hero>>(response);
if (result.IsSuccessStatusCode && result.StatusCode == HttpStatusCode.OK)
{
return data;
}
return null;
}
catch (Exception exp)
{
return null;
}
}
}
Thanks For all,
I finally solved the problem,
this is purely my fault cause I didn't really understand how it worked before,
so i try to understand more deeply about ObservableCollection and how to debug
now i know, there is nothing wrong with DeserializeObject like i said in my question (sorry for that), the problem is in GetHeroes() function
public async void GetHeroes()
{
var result = await _rest.getheroes();
if (result != null)
{
Heroes = result; // i missed this code, so. I haven't entered my data before
}
}

How do I return the original http method when using app.UseStatusCodePagesWithReExecute

Background
I want to log the original http method (post / get / head) that was used when an error was encountered.
I have a Web application in asp.net Core v3.0 and I make use of the app.UseStatusCodePagesWithReExecute("/Error/Status/{0}"); middleware to take users to an error view, which in turns, invokes the status action on my errorcontroller.
ErrorController.cs extract
[Route("[controller]/[action]/{statusCode}")]
public IActionResult Status(int? statusCode)
{
return ErrorViewModel(statusCode: statusCode);
}
[NonAction]
private IActionResult ErrorViewModel(int? statusCode = null)
{
var exception = HttpContext.Features
.Get<IExceptionHandlerFeature>();
var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
string requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
string userAgent = HttpContext.Request?.Headers["User-Agent"].ToString();
string originalMethod = ""; //WHERE CAN THIS BE DERIVED FROM?
var errorMessage = exception?.Error?.Message;
bool hasMessage = !string.IsNullOrWhiteSpace(errorMessage);
ErrorViewModel vm = new ErrorViewModel
{
ErrorMessage = errorMessage,
StatusCode = statusCode,
UserAgent = userAgent,
RequestId = requestId,
OriginalMethod = originalMethod,
OriginalPath = feature?.OriginalPath,
OriginalQueryString = feature?.OriginalQueryString
};
string logMessage = vm.GetLogMessage();
bool hasBeenLogged = false;
if (WebsiteConfig.WebsiteLogging.LogWhenGeneralError || hasMessage)
{
_logger.LogError(logMessage);
hasBeenLogged = true;
}
//if we don't want to be sent an email and don't log it as an error above, let's still log it as a piece of information for audit
if (!hasBeenLogged)
{
_logger.LogInformation(logMessage);
hasBeenLogged = true;
}
return View("Error", vm);
}
ErrorViewModel.cs
public class ErrorViewModel
{
public string ErrorMessage { get; set; }
public int? StatusCode { get; set; }
public string RequestId { get; set; }
public string UserAgent { get; set; }
public string OriginalPath { get; set; }
public string OriginalQueryString { get; set; }
public string OriginalMethod { get; set; }
public string FullOriginalPath => $"{OriginalPath}{OriginalQueryString}";
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public bool ShowGeneralError => StatusCode != 404 && StatusCode != 403 && StatusCode != 401;
public string GetLogMessage()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Request Id: {RequestId}");
sb.AppendLine($"Status code: {StatusCode}");
sb.AppendLine($"User agent: {UserAgent}");
sb.AppendLine($"Original Method: {OriginalMethod}");
sb.AppendLine($"Original FullPath: {FullOriginalPath}");
if (!string.IsNullOrWhiteSpace(ErrorMessage))
{
sb.AppendLine($"Error Message: {ErrorMessage}");
}
return sb.ToString();
}
}
Question
As you can see from above I can locate the original path and querystring using the Feature IStatusCodeReExecuteFeature. It's a shame there isn't an original method with this:
var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
...
OriginalPath = feature?.OriginalPath,
OriginalQueryString = feature?.OriginalQueryString
Can anyone tell me how I can get hold of the original method (post / get / head / etc)? I can see there's a HttpContext.Features.Method property but this is always set to "get" even if I post to an unknown action as a test.
This can be done using the original Exception that was thrown.
Using the the TargetSite property we can get the MethodBase that threw the exception, on it there is the property Name which tells you the name, and the property ReflectedType with the property Name which tells you the name of the class of the exception.
So like this:
var exception = HttpContext.Features.Get<IExceptionHandlerFeature>();
var method = exception?.Error?.Name;
var class = exception?.Error?.ReflectedType.Name;

Categories