HttpClient not sending post data to NancyFX endpoint - api

I am doing some integration testing of my web API that uses NancyFX end points. I have the xUnit test create a test server for the integration test
private readonly TestServer _server;
private readonly HttpClient _client;
public EventsModule_Int_Tester()
{
//Server setup
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_server.AllowSynchronousIO = true;//Needs to be overriden in net core 3.1
_client = _server.CreateClient();
}
Inside a Test Method I tried the following
[Fact]
public async Task EventTest()
{
// Arrange
HttpResponseMessage expectedRespone = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var data = _server.Services.GetService(typeof(GenijalnoContext)) as GenijalnoContext;
//Get come random data from the DBcontext
Random r = new Random();
List<Resident> residents = data.Residents.ToList();
Resident random_residnet = residents[r.Next(residents.Count)];
List<Apartment> apartments = data.Apartments.ToList();
Apartment random_Apartment = apartments[r.Next(apartments.Count)];
EventModel model = new EventModel()
{
ResidentId = random_residnet.Id,
ApartmentNumber = random_Apartment.Id
};
//Doesnt work
IList<KeyValuePair<string, string>> nameValueCollection = new List<KeyValuePair<string, string>> {
{ new KeyValuePair<string, string>("ResidentId", model.ResidentId.ToString()) },
{ new KeyValuePair<string, string>("ApartmentNumber", model.ApartmentNumber.ToString())}
};
var result = await _client.PostAsync("/Events/ResidentEnter", new FormUrlEncodedContent(nameValueCollection));
//Also Doesnt work
string json = JsonConvert.SerializeObject(model, Formatting.Indented);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/Events/ResidentEnter", httpContent);
//PostAsJsonAsync also doesnt work
// Assert
Assert.Equal(response.StatusCode, expectedRespone.StatusCode);
}
The NancyFX module does trigger the endpoint and receives the request but without the body
What am I doing wrong? Note that the NancyFX endpoint has no issue transforming a Postman call into a valid model.
The NancyFX endpoint

Alright I fixed it, for those curious the issue was that the NancyFX body reader sometimes does not properly start reading the request body. That is that the stream reading position isn't 0 (the start) all the time.
To fix this you need to create a CustomBoostrapper and then override the ApplicationStartup function so you can set up a before request pipeline that sets the body position at 0
Code below
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
pipelines.BeforeRequest.AddItemToStartOfPipeline(ctx =>
{
ctx.Request.Body.Position = 0;
return null;
});
}

Related

Using PublicClientApplicationBuilder and AcquireTokenSilent for chaining Web API calls

I'm working on an AD proof of concept using a console application and PublicClientApplicationBuilder to call Web API A and to call Web API B which also calls Web API A. (API A is just the "Weather" example, and API B just wraps API A.)
My call in API B to HttpContext.VerifyUserHasAnyAcceptedScope(ApiAyeScopes.AccessAsUser) keeps throwing:
IDW10203: The 'scope' or 'scp' claim does not contain scopes 'api://A0000000-1111-2222-3333-444444444444/access_as_user' or was not found.`
How can I resolve this and get the call from API B to API A to work?
I have the direct call to Web API A working. Here's how I authenticate:
static Boolean Authenticate()
{
// See the answer to https://social.msdn.microsoft.com/Forums/en-US/d4b2aff3-eeb1-4204-82ed-ca80232c2523/error-aadsts50076-due-to-a-configuration-change-made-by-your-administrator-or-because-you-moved-to?forum=WindowsAzureAD.
__identityApplication =
__identityApplication
?? PublicClientApplicationBuilder
.Create("000000-1111-2222-3333-444444444444")
.WithAuthority("https://login.microsoftonline.com/me.org/v2.0")
.WithRedirectUri("http://localhost:11596")
.Build();
string[] scopes = new string[] { "api://A0000000-1111-2222-3333-444444444444/access_as_user" };
__authenticationResult =
__identityApplication
.AcquireTokenInteractive(scopes)
.WithExtraScopesToConsent(new String[] { "api://B0000000-1111-2222-3333-444444444444/access_as_user" })
.WithUseEmbeddedWebView(false)
.ExecuteAsync()
.Result;
Console.WriteLine("Logged in as {0}.", __authenticationResult.Account.Username);
return null != __authenticationResult;
}
Here's how I call Web API A from the console, which works:
static List<WeatherForecast> GetWeatherForecast()
{
HttpClient httpClient = new HttpClient();
httpClient.Timeout = Timeout.InfiniteTimeSpan;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, __authenticationResult.AccessToken);
var response = httpClient.GetAsync("https://localhost:1001/weatherforecast").Result;
var jsonString = response.Content.ReadAsStringAsync().Result;
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<WeatherForecast>>(jsonString);
}
Here's how I call Web API B, which partially works:
static List<WeatherForecast> GetAugmentedWeatherForecast()
{
string[] scopes = new string[] { "api://B0000000-1111-2222-3333-444444444444/access_as_user" };
var apiBeeAuthenticationResult =
__identityApplication
.AcquireTokenSilent(scopes, __authenticationResult.Account)
.ExecuteAsync()
.Result;
HttpClient httpClient = new HttpClient();
httpClient.Timeout = Timeout.InfiniteTimeSpan;
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, apiBeeAuthenticationResult.AccessToken);
var response = httpClient.GetAsync("https://localhost:1101/weatherforecast").Result;
var jsonString = response.Content.ReadAsStringAsync().Result;
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<WeatherForecast>>(jsonString);
}
In Web API B, I have the following:
public class ApiAyeScopes
{
public const String WeatherRead = "api://A0000000-1111-2222-3333-444444444444/ReadWeather";
public const String AccessAsUser = "api://A0000000-1111-2222-3333-444444444444/access_as_user";
}
[AuthorizeForScopes(Scopes = new[] { ApiAyeScopes.AccessAsUser })]
[Authorize(Policy = ApiBeeAuthorizationPolicies.AssignmentToReadAugmentedWeatherRequired)]
[HttpGet]
public async Task<IEnumerable<AugmentedWeatherForecast>> Get()
{
var apiAyeScopes = new String[] { ApiAyeScopes.AccessAsUser };
// See https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-acquire-token?tabs=aspnetcore#code-in-the-controller
HttpContext.VerifyUserHasAnyAcceptedScope(apiAyeScopes);
var originalResult = await _apiAyeClient.GetWeatherForecasts();
return originalResult.Select(wf => new AugmentedWeatherForecast(wf));
}
The code to get the access token is:
String accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new String[] { ApiAyeScopes.WeatherRead });
It looks like you are looking to resolve your code from API B to API A to work and API B and API A. On-Behalf-Of flow (OBO) serves the use case where an application invokes a service/web API, which in turn needs to call another service/web API.
Learn more here:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/on-behalf-of
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
The OBO flow is represented by the steps that follow, which are illustrated in the diagram below.
More guidance can be found here: https://github.com/Azure-Samples/ms-identity-aspnet-webapi-onbehalfof

Error in ASP.NET Core MVC and Web API project

I have an ASP.NET Core MVC and also Web API project.
This error occurs when I try to send project information to the API (of course API works fine and I do not think there is a problem):
UnsupportedMediaTypeException: No MediaTypeFormatter is available to read a "TokenModel" object of "text / plain" media content.
My code is:
public class TokenModel
{
public string Token { get; set; }
}
and in AuthController I have:
var _Client = _httpClientFactory.CreateClient("MyApiClient");
var jsonBody = JsonConvert.SerializeObject(login);
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = _Client.PostAsync("/Api/Authentication", content).Result;
if (response.IsSuccessStatusCode)
{
var token = response.Content.ReadAsAsync<TokenModel>().Result;
}
The error occurs on this line:
var token = response.Content.ReadAsAsync<TokenModel>().Result;
HomeController:
public IActionResult Index()
{
var token = User.FindFirst("AccessToken").Value;
return View(_user.GetAllUsers(token));
}
UserRepository:
public List<UserViewModel> GetAllUsers(string token)
{
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var res = _client.GetStringAsync(UrlMyApi).Result;
List<UserViewModel> users = JsonConvert.DeserializeObject<List<UserViewModel>>(res);
return users;
}
Your API is returning content-type of text/plain and none of the default media type formatters(MediaTypeFormatter) which ReadAsAsync<string>() will try to use support parsing it as is. They work with JSON/XML. You can go a couple of ways but maybe the easiest is to read the content as string and deserialize it after:
var tokenJSON = response.Content.ReadAsStringAsync().Result;
var token = JsonConvert.DeserializeObject<TokenModel>(tokenJSON);
Also, as you're using the Async methods, you should be returning Task from your actions and await the result instead of using .Result as you're just creating overhead currently.
var tokenJSON = await response.Content.ReadAsStringAsync();
var token = JsonConvert.DeserializeObject<TokenModel>(tokenJSON);

Receive IFileForm in Net Core Controller and forward to another (independent) API

I have an Vue.JS application, where I upload an image to a NetCore Controller.
I'm receiving the IFileForm in the following controller:
[HttpPost("UpdateContactPhoto")]
public async Task<string> UpdateContactPhoto(IFormFile file){ //Forward the original IFileForm to another NetCore API. }
At this point everything is working correctly. IFileForm arrives perfect.
My problem is that I need to forward this IFileForm to another API (independent of this) whose input is an IFileForm with HttpClient PutAsync, but not works.
Can someone help me?
Thanks for help.
You can use this example. Note that the argument name is the same as the item added to the form-data:
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:57985");
byte[] data;
using (var br = new BinaryReader(file.OpenReadStream()))
{
data = br.ReadBytes((int) file.OpenReadStream().Length);
}
ByteArrayContent bytes = new ByteArrayContent(data);
MultipartFormDataContent multiContent = new MultipartFormDataContent();
multiContent.Add(bytes, "file", file.FileName);
var result = client.PutAsync("api/v1/FileManager", multiContent).Result;
if (result.StatusCode == HttpStatusCode.OK)
{
//do some things
}
}
You can also use this code to get the file from the HttpContext :
IFormFile file = HttpContext.Request.Form.Files[0];
Replace "Target url here" with your destination URL:
HttpClient httpClient = new HttpClient();
var streamcontent = new StreamContent(file.OpenReadStream());
var response = await httpClient.PutAsync("target url here", streamcontent);
Reference:
HttpClient PutAsync
StreamContent class
IFormFile interface

Asp.Net Core - Making API calls from backend

I have an application which is calling API's from a backend cs class, using IHostedService. With basic API calls ("http://httpbin.org/ip") it is working fine and returning the correct value, however I now need to call a Siemens API which requires me to set an Authorization header, and place "grant_type=client_credentials" in the body.
public async Task<string> GetResult()
{
string data = "";
string baseUrl = "https://<space-name>.mindsphere.io/oauth/token";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", {ServiceCredentialID: ServiceCredentialSecret});
using (HttpResponseMessage res = await client.GetAsync(baseUrl))
{
using (HttpContent content = res.Content)
{
data = await content.ReadAsStringAsync();
}
}
}
I think I have the header set up correctly but I won't know for sure until the full request gets formatted. Is it even possible to set the the body of the request to "grant_type=client_credentials"?
As far as I can see from Siemens API documentation they expect Form data, so it should be like:
public async Task<string> GetResult()
{
string data = "";
string baseUrl = "https://<space-name>.mindsphere.io/oauth/token";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", {ServiceCredentialID: ServiceCredentialSecret});
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
});
using (HttpResponseMessage res = await client.PostAsync(baseUrl, formContent))
{
using (HttpContent content = res.Content)
{
data = await content.ReadAsStringAsync();
}
}
}
}

Xamarin Android HttpClient Error when use from a Class

I have the following code that errors out when going to site what has SSL. (Error: SecureChannelFailure (The authentication or decryption has failed.) Their SSL cert is valid. When the HttpClient code is called directly there is not issue. What is wrong with my code?
Uri uri =new Uri("https://jsonplaceholder.typicode.com/posts/1");
using (HttpClient httpclient = new HttpClientClass())
{
var tt = await httpclient.GetAsync(uri);
string tx = await tt.Content.ReadAsStringAsync();
Log.Info(TAG, tx);
}
public class HttpClientClass : HttpClient
{
private HttpClient _httpclient = null;
private HttpClientHandler messagehandler = new Xamarin.Android.Net.AndroidClientHandler();
public HttpClientClass()
{
_httpclient = new HttpClient(messagehandler);
}
}
Code with No Problem
Uri uri =new Uri("https://jsonplaceholder.typicode.com/posts/1");
using (HttpClient httpclient = new HttpClient())
{
var tt = await httpclient.GetAsync(uri);
string tx = await tt.Content.ReadAsStringAsync();
Log.Info(TAG, tx);
}
Thanks to Https with TLS 1.2 in Xamarin
here is the solution. Add Nuget modernhttpclient by Paul Betts and use below. That should work within class or not.
Uri uri = new Uri("https://jsonplaceholder.typicode.com/posts/1");
using (var httpClient = new HttpClient(new NativeMessageHandler()))
{
var tt = await httpClient.GetAsync(uri);
string tx = await tt.Content.ReadAsStringAsync();
//Log.Info(TAG, tx);
}