I'm new to APIs, so I've been trying to use a web request to GET information from Reddit, since the API is unlocked. I've been able to use the right URL to get information using a REST client extension, but I want to implement the body of the data and simply just print it out to a web page.
I know this is easier with python, but they use C#/ASP.NET at my work. I've looked off of tutorials such as:
http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client
I was only able to obtain a header when I used this tutorial and my code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace API_TESTING
{
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
class Program
{
static void Main()
{
RunAsync().Wait();
}
static async Task RunAsync()
{
using(var client = new HttpClient())
{
//TODO - send HTTP requests
client.BaseAddress = new Uri("http://www.reddit.com/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//-----
HttpResponseMessage response = await client.GetAsync("r/todayilearned/new.json");
if(response.IsSuccessStatusCode)
{
Console.Write(response);
}
}
}
}
}
Can someone explain how to use ASP.NET for API calls? or link to other up-to-date tutorials? Thank you.
You're almost there. After you get the response, you need to read its content.
var response = await _client.GetAsync(uri);
if (response.IsSuccessStatusCode) {
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
You might want to check out the RedditSharp project to see how others have done this.
Btw, the tutorial you linked to does almost exactly what I answered with under the Getting a Resource (HTTP GET) section. Only difference is they used the generic ReadAsAsync while you can use ReadAsStringAsync if you're just writing the body to the console.
Related
I have a solution with two projects in Visual Studio 2022:
A console app
A minimalistic API created using ASP.NET Core Web API
The API has a simplistic model as follows:
class Todo // This is the model
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Which ought to represent Todo items in a small database, similar to the tutorial shown in the ASP.NET docs. The API has POST, PUT and DELETE methods.
The POST method looks like this:
// POST
app.MapPost("/todoitems", (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
db.SaveChanges();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
I decided to make synchronous calls to simplify this a little bit.
The console application attempts to make use of the POST method exposed by the API to add a Todo item in the database. Here's what I was able to do so far, thanks to the REST client tutorial in the Microsoft docs:
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Json;
namespace InteractWithTodoApi
{
class Program
{
private static readonly HttpClient client = new HttpClient();
private static string repoLink = "https://localhost:7157/todoitems";
private static async Task PostData()
{
JsonContent content = JsonContent.Create("{\"id\": 1, \"Name\": \"Wash dishes\", \"IsComplete\": true}", typeof(string));
HttpResponseMessage postTask = await client.PostAsync(repoLink, content);
Console.WriteLine(postTask.Content);
}
static async Task Main(string[] args)
{
await PostData();
}
}
}
I then launch the API with a localhost, and once it is up and running I execute the console app. Then I refresh the API in my browser under https://localhost:PORT/todoitems.
After I refresh my browser, I expect to see a new entry
corresponding to:
{ "Id": 1, "Name": "Wash dishes", "IsComplete": true }"
The console app doesn't crash and runs to completion, however when I refresh that browser, I see that my Todo list is still empty: [].
What am I doing wrong here?
It seems that specifying the header is important while interacting with a JSON-based API. There appear to be two ways of doing this while working with System.Net.Http
Set globally using something like:
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
Set for each POST request as follows:
string JsonString = "{\"name\": \"Wash dishes\", \"isComplete\": true}";
StringContent content = new StringContent(JsonString, Encoding.UTF8, "application/json");
The default is not application/json in System.Net.Http. It appears to be some other kind of text-based value.
Basically I created a API Project on .NET 5. My idea was to get some Repositories informations on the GitHub API and then disponibilize some informations in MY API. The request is sucessfull but, but the same error always occurs when converting Json to object
RepositoriesController:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
[ApiController]
[Route("[controller]")]
public class RepositoriesController : ControllerBase
{
private static readonly HttpClient client = new HttpClient();
private readonly ILogger<RepositoriesController> _logger;
public RepositoriesController(ILogger<RepositoriesController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get(string number)
{
return Ok(ProcessRepositories());
}
private static async Task ProcessRepositories()
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
var streamTask = client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos?sort=created&per_page=5&direction=desc");
var repositories = await JsonSerializer.DeserializeAsync<List<Repository>>(await streamTask);
}
}
Classe repository:
public class Repository
{
public string full_name { get; set; }
public string node_id { get; set; }
//public string description { get; set; }
}
But always on this part:
var repositories = await JsonSerializer.DeserializeAsync<List<Repository>>(await streamTask);
The browser return this error:
JsonException: A possible object cycle was detected. This can either
be due to a cycle or if the object depth is larger than the maximum
allowed depth of 32. Consider using ReferenceHandler.Preserve on
JsonSerializerOptions to support cycles.
I'd like to understand why the error occurs even with two simple properties with the same name as json
GitHub Api Documentation
https://docs.github.com/en/rest/reference/repos
documentation used as the basis for requesting the api:
https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/console-webapiclient#deserialize-the-json-result
Github Returned Json (I hid some properties of json because there are several)
[
{
"id": 1296269,
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
"name": "Hello-World",
"full_name": "octocat/Hello-World",
[... several hidden properties for display purposes]
}
]
Thanks in Advance :)
I suggest to switch to the Newtonsoft.Json to handle the circular references problem.
1.Install NewtonsoftJson package.
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
2.Add and configure it in ConfigureServices.
services.AddControllers().AddNewtonsoftJson(options=>
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
3.Use JsonConvert to Deserialize.
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
var streamTask = await client.GetStringAsync("https://api.github.com/orgs/dotnet/repos?sort=created&per_page=5&direction=desc");
var repositories = JsonConvert.DeserializeObject<List<Repository>>(streamTask);
I'm using Flurl Http to make http requests. In the unit tests, I'm trying to verify that the expected content was passed to the sender. I'm trying it like:
httpTest.ShouldHaveCalled(url)
.WithVerb(HttpMethod.Post)
.WithContentType(contentType)
.With(w => w.Request.Content.ReadAsStringAsync().Result == content)
.Times(1);
However, this fails with System.ObjectDisposedException Cannot access a disposed object. Object name: 'System.Net.Http.StringContent'.
It looks like Flurl is disposing the request body content before the verification is done in the test. How can I capture the request body for verification?
EDIT (A fully reproducible example):
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Autofac.Extras.Moq;
using Flurl.Http;
using Flurl.Http.Testing;
using Xunit;
namespace XUnitTestProject1
{
public class MyClassTest : IDisposable
{
private readonly AutoMock container;
private readonly HttpTest client;
public MyClassTest()
{
this.container = AutoMock.GetLoose();
this.client = new HttpTest();
}
[Fact]
public async Task SendAsync_ValidateRequestBody()
{
const string url = "http://www.example.com";
const string content = "Hello, world";
var sut = this.container.Create<MyClass>();
await sut.SendAsync(url, content);
this.client.ShouldHaveCalled(url)
.With(w => w.Request.Content.ReadAsStringAsync().Result == content);
}
public void Dispose()
{
this.container?.Dispose();
this.client?.Dispose();
}
}
public class MyClass
{
public virtual async Task SendAsync(string url, string content)
{
await url.PostAsync(new StringContent(content, Encoding.UTF8, "text/plain"));
}
}
}
In most cases (see edit below), Flurl has captured it, you just have to access it differently.
In your example, w.Request is a "raw" HttpRequestMessage, from the HttpClient stack, that Flurl exposes so you can get under the hood if you need to. HttpRequestMessage.Content is a read-once, forward-only stream that has already been read and disposed by the time you're accessing it.
To assert the captured string body, you would typically just do this instead:
httpTest.ShouldHaveCalled(url)
...
.WithRequestBody(content)
EDIT
As you noted, this doesn't work based on how you're using Flurl. The string contained by StringContent is effectively write-only, i.e. no property exposes it for reading. This is the purpose of Flurl's CapturedStringContent. If you use that type as a direct replacement for StringContent, RequestBody will be available in your test.
The reason this isn't very well covered in the docs is because if you do things "the Flurl way", you're not explicitly creating content objects in the first place. PostStringAsync and PostJsonAsync are the far more common ways to send a POST request, and both are implemented using CapturedStringContent. Use one of those methods if you can, or use PostAsync(new CapturedStringContent(...)) if you need to get at the lower-level content object for some reason.
I have a web api that I can access successfully through a browser :-
https://127.0.0.1:8443/ncrApi
I am trying to create a simple console program in C# using VS2015 to send data and receive a response using http POST.
Here is what I have so far:-
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace WebSample
{
class ApiSendData
{
public string ApiFunction { get; set; }
public string DppName { get; set; }
public string ClearData { get; set; }
public string DppVersion { get; set; }
}
class Program
{
static void Main(string[] args)
{
// The Main function calls an async method named RunAsync
// and then blocks until RunAsyncc completes.
RunAsync().Wait();
}
static async Task RunAsync()
{
using (var client = new HttpClient())
{
// This code sets the base URI for HTTP requests,
// and sets the Accept header to "application/json",
// which tells the server to send data in JSON format.
client.BaseAddress = new Uri("https://localhost:8443/ncrApi");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP POST
var datatobeSent = new ApiSendData()
{
ApiFunction ="NcrSecureData",
DppName ="CSampleCustomer",
DppVersion ="Latest",
ClearData ="1234567890"
};
HttpResponseMessage response = await client.PostAsJsonAsync("ncrApi", datatobeSent);
if (response.IsSuccessStatusCode)
{
// Get the URI of the created resource.
Uri ncrUrl = response.Headers.Location;
// do whatever you need to do here with the returned data //
}
}
}
}
}
However I am getting an error on the HttpResonseMessage response statement....{"An error occurred while sending the request."}
{"The request was aborted: Could not create SSL/TLS secure channel."}
I suspect it is because I am not correctly understanding the the client.BaseAddress and the HttpResponseMessage response statements.
Here is what I have been following:-
http://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client
You are probably getting an error because the final address is your baseAddress + post address, that is: http://localhost:8443/nrcApi/nrcApi , which doesn't exist
Try changing your client.BaseAddress to:
client.BaseAddress = new Uri("https://localhost:8443/");
For SSL connection errors, try generating a trusted certificate:
Make https call using httpclient
Your code looks ok. However, the issue seems to be the call you are making. Basically in the following call, the first parameter is the method/function to be invoked after your URI.
HttpResponseMessage response = await client.PostAsJsonAsync("ncrApi",
datatobeSent);
In this case ncrApi is your method. Calling it in the base URL will result in it being added to the final call making it give an address to an endpoint that does not exist.
In response of another SO questions, I came across a problem when running async Task with Xunit and visual studio 2015 ctp6.
here is the code:
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Xunit;
using Microsoft.AspNet.Builder;
using System.Net.Http;
namespace Multi.Web.Api
{
public class TestServerHelper : IDisposable
{
public TestServerHelper()
{
ClientProvider = new TestClientProvider();
ApiServer = TestServer.Create((app) =>
{
app.UseServices(services =>
{
services.AddTransient<IClientProvider>(s => ClientProvider);
});
app.UseMulti();
});
}
public TestClientProvider ClientProvider { get; private set; }
public TestServer ApiServer { get; private set; }
public void Dispose()
{
ApiServer.Dispose();
ClientProvider.Dispose();
}
}
public class MultiMiddlewareTest : IClassFixture<TestServerHelper>
{
TestServerHelper _testServerHelper;
public MultiMiddlewareTest(TestServerHelper testServerHelper)
{
_testServerHelper = testServerHelper;
}
[Fact]
public async Task ShouldReturnToday()
{
using (HttpClient client = _testServerHelper.ApiServer.CreateClient())
{
var response = await client.GetAsync("http://localhost/today");
String content = await response.Content.ReadAsStringAsync();
Assert.Equal(content, "2015-04-15 count is 1");
}
}
[Fact]
public async Task ShouldReturnYesterday()
{
using (HttpClient client = _testServerHelper.ApiServer.CreateClient())
{
var response = await client.GetAsync("http://localhost/yesterday");
String content = await response.Content.ReadAsStringAsync();
Assert.Equal(content, "2015-04-14 count is 1");
}
}
}
}
in visual studio TestExplorer, when running the test one by one (right click and select debug selected test) it's ok, but when running all, none of the passes and I have the following error
Message : Response status code does not indicate success : 404 (Not Fount)
all the code is available on the other so question, in that question, I answered on how to use multiple instance of TestServer to mock external Api. And I think it has to do with some Synchronization context.
I think I wrote my Helper not in a good way because I see it disposes objects before the call is actually done (sometimes not...). does someone had the same issue and had a solution on this ?
UPDATE : link to full code on github
Xunit is running your tests in parallel by default when you run all tests; I'm guessing this is probably causing your test servers to collide and have side effects. (I'm not sure what all packages you're using in your project.json, so I could be mistaken.) Take a look at Running tests in parallel in the Xunit documentation and find the solution right for you.
Options:
Use the CollectionAttribute such as [Collection("Localhost http server")].
Specify -parallel none as the option on the command line.
Change the default behavior using an assembly attribute such as [assembly: CollectionBehavior(DisableTestParallelization = true)]
UPDATE 2:
Update 1 was a red herring, so I removed it entirely. Removing await next(context); from the FakeExternalApi middleware seems to have removed the intermittent issues. A comment from #Tratcher indicates that "calling Next ... is discouraged," but I'm not sure if that's specifically related to OwinMiddleware or if it is good general advice, but it seems to apply here.