How to post a string array using Flurl? - c#

In UI, the input type for SourceOfWealthOrIncome is a checkbox. In the front-end Razor page, I am using Flurl to issue a POST request.
var baseUrl = configuration.GetSection("xxx").GetValue<string>("Url");
string endPoint = "/Api/test/";
string url = string.Format("{0}{1}{2}", baseUrl, endPoint, ecddClientId);
var response = await url.SetQueryParams(new { id = ecddClientId, dealerCode })
.PostMultipartAsync(mp =>
{
mp = mp.AddString("SourceOfWealthOrIncome", postData.SourceOfWealthOrIncome);
foreach (var doc in postData.SupportingDocs)
{
mp = mp.AddFile("SupportingDocs", doc.FileContent, doc.FileName);
}
});
Troubleshoot: If I changed from SourceOfWealthOrIncome[] to SourceOfWealthOrIncome, I can see the values of the checkbox for example "value1, value2". But I can't change the endpoint code. This is the code for endpoint in C# in .NET Framework 4.x.
[HttpPost]
public async Task<IHttpActionResult> PutEnhancedCdd(int id, string dealerCode)
{
string root = Path.GetTempPath();
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
var sourceOfWealthOrIncome = provider.FormData["SourceOfWealthOrIncome[]"];
return Ok();
}
How do I change my front-end code using Flurl so that I can send a string object over to this current endpoint?

Related

Problem accessing passed parameter in url redirection

Hello I'm trying to do an Oauth 2.0 to integrate two systems together,
and it uses oauth so I have successfully been able to get the auth code from the url after authenticating, but I couldn't get the parameter from the redirect url to use it and generate the token within the code.
public void getData()
{
var query = "Store-url?scope=offline_access&state=12345678&response_type=code&approval_prompt=auto&redirect_uri=localhost/api/Omar/test&client_id=";
Response.Redirect(query);
}
this is the redirect url which take me to the store website to give the access and then it return the auth code in the url like this:
https://localhost/api/Omar/test?code=OPCKC8KwzcTPUWnvJsA-apz09-PsDNrDYTrUfffMIDU.pnjIZahbJBxC9G_BY4KEom2LNkPHKbnojJCeotylcKA&scope=settings.read+products.read_write+offline_access&state=12345678
and I tried the code to generate the token and it worked
the problem is how I get store the code passed in the parameter so I can use in my method that generates the token within the code
and the method code is
[HttpGet("test")]
public async Task<IActionResult> test(string code)
{
var values = new Dictionary<string, string>
{
{ "client_id", "" },
{ "client_secret", "" },
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", "localhost/api/Omar/test" }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("store-generate-token-url", content);
var responseString = await response.Content.ReadAsStringAsync();
return Ok("TEST SUCCESS"+ " "+ responseString);
}

C# WebApi - Sending long text in HTTPRequest not working

I am working on a WebApp (Razor Pages) that work also as API Gateway. The WebApp get some data from another project (part of the same solution) that is a WebAPI.
The problem is that when I do an HTTPRequest to the WebAPI, if the request is not too long, the WebAPI will process it, but when I try to send a longer request (long in characters) it will reject it and send back a 404.
The WebApp is a basic CMS. So the app will provide to the user, the creation of Web pages. I am using a restful request model so a request will look like this:
string baseURL = #"https://localhost:5001";
public async Task<string> CreatePageAsync(string pageTitle, string pageBody, int? pageOrder, string userID)
{
if (pageTitle != null && pageBody != null && pageOrder != null && userID != null)
{
string fullURL = baseURL + $"/api/pages/create/page/title/{pageTitle}/body/{pageBody}/order/{pageOrder}/user/{userID}";
var request = new HttpRequestMessage(HttpMethod.Post, fullURL);
HttpResponseMessage response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
return "error";
}
}
return "ok";
}
As you can imagine, the "pageBody" property is the one responsible for the length of the request. So, when I test the WebAPI with short words, it works just fine, but if I copy an article from the internet (Just text) and use it as the body (simulating the user's content), if it is a long one, it will return a 404.
On the other end, the WebAPI looks like this:
[HttpPost("Create/page/title/{pageTitle}/body/{pageBody}/order/{pageOrder}/user/{userID}")]
//[ValidateAntiForgeryToken]
public async Task<string> CreatePage(string pageTitle, string pageBody, int pageOrder, string userID) //[Bind("pageName,pageHead,pageBody,userID")]
{
if (ModelState.IsValid)
{
DateTime now = DateTime.Now;
WebPage newPage = new WebPage()
{
PageID = _globalServices.GuidFromString(_globalServices.GetSeed()),
PageDateCreated = now,
PageDateUpdated = now,
PageOrder = pageOrder,
PageTitle = pageTitle,
PageBody = pageBody,
UserID = userID
};
try
{
await _pagesDBContext.Pages.AddAsync(newPage);
await _pagesDBContext.SaveChangesAsync();
}
catch (Exception e)
{
string message = "ERROR: Could not save to the database.\n";
return message + e.Message;
}
return "Page saved";
}
return "ERROR: Model invalid";
}
I am sending the request as simple text. I don't know if there is a better way.
Any ideas?
I don't have enough rep to comment but it looks like the maximum characters you can send in a GET request is 2,048.

How to test a controller POST method which returns no data in response content in .NET Core 3.1?

i am new to integration tests. I have a controller method which adds a user to the database, as shown below:
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
try
{
var command = new CreateUserCommand
{
Login = request.Login,
Password = request.Password,
FirstName = request.FirstName,
LastName = request.LastName,
MailAddress = request.MailAddress,
TokenOwnerInformation = User
};
await CommandBus.SendAsync(command);
return Ok();
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
As you have noticed my method returns no information about the user which has been added to the database - it informs about the results of handling a certain request using the status codes. I have written an integration test to check is it working properly:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "Aleksander",
LastName = "Kowalski",
MailAddress = "akowalski#onet.poczta.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
I am not sure is it enough to assert just a status code of response returned from the server. I am confused because, i don't know, shall i attach to assert section code, which would get all the users and check does it contain created user for example. I don't even have any id of such a user because my application finds a new id for the user while adding him/her to the database. I also have no idea how to test methods like that:
[HttpGet("{userId:int}")]
public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
{
try
{
var query = new GetUserQuery
{
UserId = userId,
TokenOwnerInformation = User
};
var user = await QueryBus
.SendAsync<GetUserQuery, UserDto>(query);
var result = user is null
? (IActionResult) NotFound(new
{
Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
})
: Ok(user);
return result;
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
I believe i should somehow create a user firstly in Arrange section, get it's id and then use it in Act section with the GetUserAsync method called with the request sent by HttpClient. Again the same problem - no information about user is returned, after creation (by the way - it is not returned, because of my CQRS design in whole application - commands return no information). Could you please explain me how to write such a tests properly? Have i missed anything? Thanks for any help.
This is how I do it:
var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);
The second line above is not necessary, just there for illustration.
Also, your response it's better when you return a 201 (Created) instead of the 200(OK) on Post verbs, like:
return Created($"api/users/{user.id}", user);
To test NotFound's:
var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);
The NotFoundObjectResult assumes you are returning something. If you are just responding with a 404 and no explanation, replace NotFoundObjectResult with a NotFoundResult.
And finally InternalServerErrors:
var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
You can use integrationFixture for that using this NuGet package. This is an AutoFixture alternative for integration tests.
The documented examples use Get calls but you can do other calls too. Logically, you should test for the status code (OkObjectResult means 200) value and the response (which could be an empty string, that is no problem at all).
Here is the documented example for a normal Get call.
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
In the example above, the controller is resolved using the DI described in your Startup class.
You can also do an actual REST call using using Refit. The application is self hosted inside your test.
using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var refitClient = fixture.GetRefitClient();
var response = await refitClient.GetNumberOfCharacters("Hoi");
await response.EnsureSuccessStatusCodeAsync();
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
}
}

Read image from private GitHub repository programmatically using C#

Need to store the image from a private git repository to a blob using C#. Tried with below code but getting 404 errors.
I am using the below code from
C# example of downloading GitHub private repo programmatically
var githubToken = "[token]";
var url =
"https://github.com/[username]/[repository]/archive/[sha1|tag].zip";
var path = #"[local path]";
using (var client = new System.Net.Http.HttpClient())
{
var credentials = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}:", githubToken);
credentials = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(credentials));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);
var contents = client.GetByteArrayAsync(url).Result;
System.IO.File.WriteAllBytes(path, contents);
}
Note: Able to fetch from the public repository
How to fix :
The URL is changed to GET /repos/:owner/:repo/:archive_format/:ref. See https://developer.github.com/v3/repos/contents/#get-archive-link
For private repositories, these links are temporary and expire after five minutes.
GET /repos/:owner/:repo/:archive_format/:ref
You should not pass the credentials using basic authentication. Instead, you should create a token by following the official docs. see https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
Finally, you need pass an extra User-Agent header. It is required by GitHub API. See https://developer.github.com/v3/#user-agent-required :
All API requests MUST include a valid User-Agent header.
Demo
public class GitHubRepoApi{
public string EndPoint {get;} = "https://api.github.com/repos";
public async Task DownloadArchieveAsync(string saveAs, string owner, string token, string repo,string #ref="master",string format="zipball")
{
var url = this.GetArchieveUrl(owner, repo, #ref, format);
var req = this.BuildRequestMessage(url,token);
using( var httpClient = new HttpClient()){
var resp = await httpClient.SendAsync(req);
if(resp.StatusCode != System.Net.HttpStatusCode.OK){
throw new Exception($"error happens when downloading the {req.RequestUri}, statusCode={resp.StatusCode}");
}
using(var fs = File.OpenWrite(saveAs) ){
await resp.Content.CopyToAsync(fs);
}
}
}
private string GetArchieveUrl(string owner, string repo, string #ref = "master", string format="zipball")
{
return $"{this.EndPoint}/{owner}/{repo}/{format}/{#ref}"; // See https://developer.github.com/v3/repos/contents/#get-archive-link
}
private HttpRequestMessage BuildRequestMessage(string url, string token)
{
var uriBuilder = new UriBuilder(url);
uriBuilder.Query = $"access_token={token}"; // See https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
var req = new HttpRequestMessage();
req.RequestUri = uriBuilder.Uri;
req.Headers.Add("User-Agent","My C# Client"); // required, See https://developer.github.com/v3/#user-agent-required
return req;
}
}
Test :
var api = new GitHubRepoApi();
var saveAs= Path.Combine(Directory.GetCurrentDirectory(),"abc.zip");
var owner = "newbienewbie";
var token = "------your-----token--------";
var repo = "your-repo";
var #ref = "6883a92222759d574a724b5b8952bc475f580fe0"; // will be "master" by default
api.DownloadArchieveAsync(saveAs, owner,token,repo,#ref).Wait();
According to the message you provide, you use the wrong url to download. Regarding how to get the download url, please refer to the following steps:
Use the following url to get the download url
Method: GET
URL: https://api.github.com/repos/:owner/:repo/contents/:path?ref:<The name of the commit/branch/tag>
Header:
Authorization: token <personal access token>
The repose body will tell you the download url
For example :
Download file
For more details, please refer to https://developer.github.com/v3/repos/contents/#get-contents.

How can I upload a file and form data using Flurl?

I'm trying to upload a file with body content. Is PostMultipartAsync the only way?
On my C# backend code I have this:
var resource = FormBind<StorageFileResource>();
var file = Request.Files.First().ToPostedFile();
FormBind reads data from the request and fills the object.
By using PostMultipartAsync I know it should start like this:
.PostMultipartAsync((mp) => { mp.AddFile(name, stream, name)}), but I can't figure out how to add the object. Do you have any ideas on that?
This is my current try:
public static async Task<T> PostFileAsync<T>(string url, object data, string name, Stream stream, object queryString = null)
where T : class
{
return await HandleRequest(async () => queryString != null
? await url
.SetQueryParams(queryString)
.SetClaimsToken()
.PostMultipartAsync((mp) => { mp.AddFile(name, stream, name)})
.ReceiveJson<T>()
: await url
.SetClaimsToken()
.PostMultipartAsync((mp) => mp.AddFile(name, stream, name))
.ReceiveJson<T>());
}
Current request being made by the front end:
There are a variety of ways to add "parts" to a multipart POST with Flurl. I haven't added this to the docs yet but here's an example from the issue that basically demonstrates every possibility:
var resp = await "http://api.com"
.PostMultipartAsync(mp => mp
.AddString("name", "hello!") // individual string
.AddStringParts(new {a = 1, b = 2}) // multiple strings
.AddFile("file1", path1) // local file path
.AddFile("file2", stream, "foo.txt") // file stream
.AddJson("json", new { foo = "x" }) // json
.AddUrlEncoded("urlEnc", new { bar = "y" }) // URL-encoded
.Add(content)); // any HttpContent
Here is one way that works for me
var result = await endPointApi
.AppendPathSegments("api","AppFileManager")
.WithOAuthBearerToken(token.AccessToken)
.PostMultipartAsync(mp => mp
//.AddFile("UploadFile", #"C:\Users\..\Documents\upload.txt")
.AddFile("UploadFile", new MemoryStream(data), appFile.FileName)
.AddStringParts(new
{
IRN = appFile.IRN,
TransactionIRN = appFile.TransactionIRN,
FileName = appFile.FileName,
TableName = appFile.TableName,
FileExtension = appFile.FileExtension,
})
Web Api Controller Implementation (using MediatR)
[HttpPost]
public async Task<IActionResult> Post([FromForm] AppFileManagerCommands.Upload uploadAttachment)
{
await mediator.Send(uploadAttachment);
return NoContent();
}

Categories

Resources