2

I'm new on asp.net core with .NET7.0, and I'm trying to develop a simple web app that comunicates with a PC on the same LAN and responds to an http POST sent by the web server. To send the httpPOST I use the HttpClient library. First of all, my Program.cs looks like this:

using Microsoft.AspNetCore.Cors;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
using System.Net;
using System.Reflection.PortableExecutable;

var builder = WebApplication.CreateBuilder(args);

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.AllowAnyOrigin();
                          policy.AllowAnyHeader();
                          policy.AllowAnyMethod();
                      });
});


// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPagesMovieContext") ?? throw new InvalidOperationException("Connection string 'RazorPagesMovieContext' not found.")));
builder.Services.AddCors();
builder.Services.AddControllers();

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.MapRazorPages();

app.Run();

Then, the method that I use to send a POST is this:

[HttpOptions]
[EnableCors("_myAllowSpecificOrigins")]
private async void sendMessage(string message)
{
    using (var httpClient = new HttpClient())
    {
        var content = new StringContent(message, Encoding.UTF8, "text/plain");
        var response = httpClient.PostAsync("http://ip:8000/", content).Result;

        if (response.IsSuccessStatusCode)
        {
            Console.WriteLine("Success");
        }
        else
        {
            Console.WriteLine("Fail");
        }
    }
}

Once I've explained this, I'll expose my error. Once I start the web app, I start a script on linux that constantly listens to any http message sent. This script should receive a message when I click a certain button that launches the http POST method, but the nothing is sent.

The message is sent when the app reaches its timeout and shows this warning: var response = httpClient.PostAsync("http://ip:8000/", content).Result; System.AggregateException: 'One or more errors occurred. (A task was canceled.)'

Also it is sent when, while the app loads trying to send it I close the server program.

My guess is that it is sent in these occasions because in that moment the ip/port is not occupied (I'm no expert at all in this).

Also, as you can see I've implemented the solution obvserved in other stackoverflow questions of the CORS problem... and nothing worked.

Does somebody know what might be the issue?


Edit:

I've sniffed the server-side with wireshark and I've seen the following behavoir:

  1. The HTTP connection is stablished by the typical 3-step handshake.
  2. The POST request is successfully sent and acknowledged by the client.
  3. During the next 100 seconds the PostAsync() method is waiting for a response that is never sent.
  4. When the timeout is reached the method launches an exception and decides to request the end of the http socket, which is succesfully done(I think so).
  5. Then the client prints "Answered to the request: ..." (don't know exactly if that happens before or after the [ACK+FIN] TCP packets, it is almost simultaneous)

Below you can see the TCP/HTTP frames sniffed: [![Traffic sniffed during problem reproduction][1]][1]

To confirm that the error is server-side, here you have the code used in my linux terminal:

#!/bin/bash

PORT=5555

while true; do
    message=$(nc -l -p $PORT | grep -i "HELLO")

    if [[ ! -z "$message" ]]; then
        echo "How are you?" | nc -q 0 server_ip 5555
        echo "Answered to the request: $message"
    fi
done

PD: As you can see the string HELLO is what I send on the POST request.

  • First of all you could send the request with something like Postman. To confirm the API is working as expected. If that is the case the problem could be client side or within the connection. – jeb Jul 26 '23 at 18:58
  • Hi, I tried to make a requests with postman and it did not work either... I've edited my post btw. – Roger Almirall Jou Jul 28 '23 at 13:33
  • From what i can see : your method should not be [async void](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming). and you should recreate the [httpclient](https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines) yourself. – jeb Jul 28 '23 at 13:54
  • You mapped both echo and echo2 to Gets (MapGet), not sure if that's a typo or... Should be MapPost if you want it to accept post requests. – Mike Jul 28 '23 at 15:59

3 Answers3

2

If the error occurs on the client side:
You should await the result of the request. So your line

var response = httpClient.PostAsync("http://ip:8000/", content).Result;

will become

var response = await httpClient.PostAsync("http://ip:8080/", content);

If the error occurs on the server side: You are not properly mapping the endpoints. First of all, if you want to POST, you should map a POST request. But also, your GET's are not completely right.

You are making it very difficult for yourself by writing to the body in the HttpContext. Better would be, to just build it like this:

() => "echo"

This also removes the need to inject the HttpContext.

so for the request you are sending, you should make an endpoint like this:

endpoints.MapPost("/echo", () => "echo")
         .RequireCors(MyAllowSpecificOrigins);

If it still is not working, have you attempted to just call the endpoints on localhost?


EDIT: additional possible solution 1

  1. Add a simple MapGet, like
endpoints.MapGet("/", () => "Hello, World!");
  1. Start the program
  2. In the console window you see something like this: console output with text 'Now listening on https://localhost:4300' On that same machine, go to a browser and type that exact path printed there. So in my case 'https://localhost:4300', but the 'https' and '4300' may vary.
  3. You should just see the 'Hello, World!'.
Jonathan
  • 330
  • 1
  • 10
  • Thanks for the suggestion, but it still does not work... my POST request is still not working... Now it even gets sent like with a 2 minutes delay. I've also tried to change the port and it does not solve it. Any further suggestions? – Roger Almirall Jou Jul 26 '23 at 09:02
  • @RogerAlmirallJou see the edit. Does that work for you? And if not, does it in an empty project? – Jonathan Jul 26 '23 at 18:17
  • Thanks, I've tried it and it does work, however I don't know what advantage does this gives me, because this is for POST requests from the client and not TO the client... doesn't it? PD: I've updated my post with further information to try to find the problem source. – Roger Almirall Jou Jul 28 '23 at 13:35
  • Do you possibly have a GitHub repository for it? Then I can clone it and look at the code myself. Otherwise: Are you sure the POST has been mapped? And if you add a breakpoint in the RequestDelegate's body, does it get hit? – Jonathan Jul 28 '23 at 14:04
  • 1
    Thanks to the last answer, I realized the correct way to do It. And since your EDIT has finally been what I needed I'll mark this answer as the solution. – Roger Almirall Jou Aug 02 '23 at 10:52
  • Amazing! Good luck with your project! – Jonathan Aug 03 '23 at 11:03
1

If you are running your API app (the one getting the request) in an IDE like VS or code, it's probably breaking on the exception and waiting for input. Once you close the app, the breakpoint is resolved, and finally, the code finishes executing and completes the HTTP request. Look at your API app for your debugging controls, probably near the top of your screen:

(from VS Code)

debugging controls in VS Code

J Scott
  • 781
  • 9
  • 12
  • I don't know if that is what you were suggesting but I run it once published with an .exe and it still didn't work. Was that what you meant? – Roger Almirall Jou Jul 26 '23 at 09:22
  • IDEs used to build code often 'break' execution on an error, where the app pauses and waits for instructions so you can investigate what happened. While the app is paused, the request will be stuck waiting for your server, which itself is waiting for your input. When you close the app, the request is terminated as part of the shutdown. – J Scott Jul 27 '23 at 15:13
1

You can use app.MapPost

app.MapPost("/echo", async context =>
{
    var request = context.Request;
    if (request.ContentLength > 0)
    {
        request.EnableBuffering();
        var buffer = new byte[Convert.ToInt32(request.ContentLength)];
        await request.Body.ReadAsync(buffer, 0, buffer.Length);
        var requestContent = Encoding.UTF8.GetString(buffer);

        await context.Response.WriteAsync($"Servo echoes: {requestContent}\n");
    }
    else
    {
        await context.Response.WriteAsync("Servo says Hello!");
    }
}).RequireCors(MyAllowSpecificOrigins);

and create a client like (change address/port as needed):

using System.Text;

async Task callEcho(string message)
{
    using HttpClient client = new();

    try
    {
        var content = new StringContent(message, Encoding.UTF8, "text/plain");

        using HttpResponseMessage response = await client.PostAsync("https://localhost:7093/echo", content);

        response.EnsureSuccessStatusCode();

        var responseBody = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Response: {responseBody}\n");
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine("\nException Caught!");
        Console.WriteLine("Message :{0} ", e.Message);
    }
}

await callEcho("This is the Console Client Calling!");
Mike
  • 3,186
  • 3
  • 26
  • 32
  • Thanks for your answer. With this one I realized that what I needed to do was to make the client wait for a response after requesting GET and whenever the server wants it can POST. To sum up, I used the code in your answer but I made the POST being in a method that waits for whatever things will be done. I'll mark it as solved. – Roger Almirall Jou Aug 02 '23 at 10:50