c# WebAPI API Issue - Swagger - Angular Axios - c#

I have an issue and I got no idea what to do, the issue is that on my C# WebApi app with swagger enabled.
i have a few apis but here is an example 1 of them.
[HttpPost]
[Route("/api/user/register")]
public UserSession Register(string email, string password, string confirm_password)
{
if (password != confirm_password)
{
return new UserSession()
{
Success = false,
Message = "Error Passwords don't match",
SessionKey = "",
};
}
// success code here
}
here is the angular API.
import axios from 'axios';
export class API {
private static base_api:string = "http://localhost:51019/";
static register(email:string, password:any, confirm_password:any) {
let url = this.base_api + "api/user/register";
let data = {
email: email,
password: password,
confirm_password: confirm_password,
};
const headers = {
'Content-Type': 'application/json'
};
let result = axios.post(url, data, {headers}).then(x=>{return x}).catch(x=>{return false;});
console.log(result);
}
}
even when I provide an email and/or a password, it's like the API isn't receiving the data?
to fix the cor issue i had i added this to the api controller
[HttpOptions]
[Route("/api/user/register")]
[Route("/api/user/login")]
[Route("/api/user/logout")]
public HttpResponseMessage Options()
{
var response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
return response;
}
if i access the api via the swagger ui via (http://localhost:51019/swagger/index.html)
then when i perform the api via the UI it works correctly.
TIA

Typically you'd create a model and set it as the body:
public sealed class RegisterModel
{
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
[JsonPropertyName("confirm_password")]
public string ConfirmPassword { get; set; }
}
[HttpPost, Route("/api/user/register")]
public UserSession Register([FromBody] RegisterModel register)
{
// ...
}
That will get it to work in all scenarios

Related

How to make use of IFormFile and attach it to the DTO to be sent to client in ASP.NET Core

I am writing a web app using .NET for the backend and React as client. I want to implement the authorization to a login form and thus made two model (DTOs) to send back to the client. One for the login and register respectively. I have an AccountController class where I am handling the Post and Get requests for the current user (user after logged in).
As mentioned above I have a model class named User and its UserDTO. Inside of that model class, I made some props (eg. username, password, profilePictureURL, email) etc. I want the user to upload an image which in turn will get appended to the profilePictureURL into the request back to the server.
This is my User model:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace API.Entities
{
public class User : IdentityUser
{
public IFormFile profilePhotoURL {get; set; }
public string Name { get; set; }
}
}
userName, email and phone numbers are being derived from IdentityUser class from .NET.
registerDTO
using Microsoft.AspNetCore.Http;
namespace API.DTOs
{
public class RegisterDTO : LoginDTO
{
public string Email { get; set; }
public string Name { get; set; }
public FormFile profilePhotoURL { get; set; }
public string PhoneNumber { get; set; }
}
}
Inside of AccountController, my POST method for registering is:
[HttpPost("register"), DisableRequestSizeLimit]
public async Task<IActionResult> RegisterUser(RegisterDTO registerDTO)
{
//getting the file from request
var postedProfile = Request.Form.Files[0];
// setting the Uploads folder
var Uploads = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
if (postedProfile.Length > 0)
{
var fileName = ContentDispositionHeaderValue.Parse(postedProfile.ContentDisposition).FileName.Trim();
var pathToSave = Path.Combine(Uploads, fileName.ToString());
using (var fileStream = new FileStream(pathToSave, FileMode.Create))
{
await postedProfile.CopyToAsync(fileStream);
}
Ok($"File Uploaded successfully");
}
else
{
return BadRequest(new ProblemDetails
{
Title = "400 - Bad Request",
Status = 400,
Detail = "File not uploaded"
});
}
var registeredUser = new User
{
UserName = registerDTO.userName,
Email = registerDTO.Email,
Name = registerDTO.Name,
profilePhotoURL = postedProfile,
PhoneNumber = registerDTO.PhoneNumber
};
var result = await _userManager.CreateAsync(registeredUser, registerDTO.Password);
if (!result.Succeeded)
{
foreach (var Error in result.Errors)
{
ModelState.AddModelError(Error.Code, Error.Description);
}
return ValidationProblem();
}
await _userManager.AddToRoleAsync(registeredUser, "Member");
return StatusCode(201);
}
I want to set the uploaded image into the profilePhotoURL property of my registerDTO class, but when on checking this in swagger, I get the following error:
The JSON value could not be converted to Microsoft.AspNetCore.Http.FormFile. Path: $.profilePhotoURL | LineNumber: 5 | BytePositionInLine: 36.
The above error is in the ModelState errors.
How could I make a method (inside of AccountController's POST request for registering a new user) to upload a file of type IFormFile and then set it inside of the User object?
All suggestions are welcome :)
Your backend was a webapi project, model binding get data from the request body(Json value) by default,but formfile get values from posted form fields.
If you could post a form in your react app,just create a model for the form and add the [FromForm]Attribute
[HttpPost("register"), DisableRequestSizeLimit]
public IActionResult RegisterUser([FromForm] RegisterModel registermodel)
{
return StatusCode(200);
}

How can I use a token from a request for other requests after responding to the first request?

I have a scenario where I need to respond to a request that it's been received and send a response (request?) to another endpoint once internal api calls and logic has completed. The flow looks like this:
External request to my endpoint > endpoint responds to request with accepted > endpoint passes the request on internally > internal logic fetches and handles data from DB > internal logic uses data from DB to send a request back to a different endpoint from the same integration as the first call came from.
I have managed to get it to work using Queued Background Tasks to send the request to the correct internal handler with Mediatr. However in order for it to work I need to add the barer token from the request header to the request object and then use that barer token to validate against the internal API's. I'd like to avoid this since I might run into the issue of the token expiring or not being valid for the internal Api etc.
Request object example:
public class ExampleRequest : IRequest, IRequest<ExampleResponse>
{
public string? Token { get; set; } //Added for functioning version, want to get rid
//of it
public CommonData Data { get; set; }
public string RequestId { get; set; }
public string OperationId { get; set; }
public List<string> ObjectIdentifiers { get; set; }
}
public class CommonData
{
public string MessageId { get; set; }
public DateTime Timestamp { get; set; }
}
Response object example (response to the call):
public class ExampleResponseForCall
{
public CommonData Data { get; set; }
public string ResponseStatus { get; set; } //Will be accepted/acknowledged
}
Example response object (for final response)
public class ExampleResponse
{
public CommonData Data{ get; set; }
public string ResponseStatus { get; set; }
public string ErrorCode { get; set; }
public string ErrorDescription { get; set; }
public string RequestId { get; set; }
public string OperationId { get; set; }
}
My current working version looks something like this:
**Endpoint:**
public IActionResult Post(ExampleRequest request)
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
}
var token = headerValue?.Parameter;
request.Token = token; //I added token as a nullable string on the request obj
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
**Background Task queue:**
public void StartTask(IRequest request)
{
_logger.LogInformation("Task is starting.");
_request = request;
Task.Run(async () => await AddTaskAsync(), _cancellationToken);
}
private async ValueTask AddTaskAsync()
{
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Task {Guid} is starting.", guid);
if (_request == null)
{
_logger.LogWarning("Request for task {Guid} is null.", guid);
return;
}
await _mediator.Send(_request, token);
_logger.LogInformation("Task {Guid} is complete.", guid);
}
I also have Handlers that can handle the request and Clients for sending requests internally and back to the caller. All of that works when awaiting the internal logic to be handled. However when I'm using the background task queue the internal client fails on the when getting the token here
protected async Task<HttpClient> CreateBaseClient()
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", await GetToken());
return client;
}
public async Task<string> GetToken()
{
if (_httpContextAccessor.HttpContext == null)
throw new Exception("No HttpContext available when trying to
get Token.");
_httpContextAccessor.HttpContext.Items.TryGetValue(Constants.AuthenticationSchemeKey,
out var scheme);
if (scheme?.ToString() == Constants.Bearer)
return GetTokenFromRequest();
throw new MissingAccessTokenException("Unknown authentication type");
}
My workaround (that I want to get away from) looks like this:
protected async Task<HttpClient> CreateBaseClient(string version, string token)
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", token); //token from requst.Token
return client;
}
I've tried to pass in the a lot of different things to the Background Task queue (and changing the parameter type to match ofc) but nothing works. I want to have the background task queue generic since I'll be implementing this for other end points as well.
That's a lot of text so TL:DR, I respond to a request but need to use the token from the request for other calls after responding.
We decided to go with the working solution provided in the question itself.
Due to how our infrastructure is set up we won't be able to get a refresh token (as suggested by #GHDevOps and #TheWallrus) since we won't be able to get the login/id and password/secret of the user in a safe and reasonable way.
However, the working solution in the question has some drawback which should be analyzed on a case-to-case basis. We know that the Api sending us the requests will fetch a new (relevant) token approximately 10 minutes before the current (relevant) token expires and use the new token for all coming requests. Since the logic we apply before passing on the request to our backend is very simple (just simple remapping) we should rarely run into issues with the token expiring before the request has been sent, and in the rare cases that is has, we will send that information in the request back to the external Api, giving them a chance to resend the original request. If the external Api isn't fetching a new token before the expiration of the current token that might cause the token to expire before reaching the internal Api more often which might be a good thing to look after if you're implementing a similar solution.
The code changes that I made for this to function are just minor refactoring (see below). Hope this help anyone else running into a similar issue!
//Endpoint
public IActionResult Post(ExampleRequest request)//Before
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
}
var token = headerValue?.Parameter;
request.Token = token; //I added token as a nullable string on the request obj
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
public IActionResult Post(ExampleRequest request)
{
request.Token = GetToken(Request);//Made into a separate function in the inherited class
_backgroundTaskQueue.StartTask(request);
return Ok(new ExampleResponseForCall
{
Data = request.Data,
ResponseStatus = HttpStatusCode.Accepted.ToString()
});
}
protected string GetToken(HttpRequest request)
{
var authorization = request.Headers[HeaderNames.Authorization];
_ = AuthenticationHeaderValue.TryParse(authorization, out var headerValue);
if (headerValue == null)
{
return "";
}
return string.Equals(headerValue.Scheme, "Bearer", StringComparison.InvariantCultureIgnoreCase) ?
headerValue.Parameter : "";
}
//Client
protected async Task<HttpClient> CreateBaseClient()//before
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept",
$"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", await GetToken());
return client;
}
protected async Task<HttpClient> CreateBaseClient(string token = "")
{
var client = _clientFactory.CreateClient(HttpClientName);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", $"application/json");
client.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("Bearer", string.IsNullOrEmpty(token) ?
await GetToken() : token); //We will only send in a token if we are async
return client;
}

Unable to submit data to action method of .net core from angular 5

I am trying to post data from angular 5 component to action method of .net core. I am able to hit the action method but values are null. As per below example Usr.FirstName is null in Sush action method.
Model-
namespace VModels
{
public class UserVM
{
public long UserId { get; set; }
public string FirstName { get; set; }
}
}
Action method of .net core-
[HttpPost]
public IActionResult Sush(UserVM Usr)
{
UserVM objOutput = new UserVM();
CommonGetPostMethod<UserVM, UserVM> objCommonMthd = new CommonGetPostMethod<UserVM, UserVM>();
UserVM objvm = new UserVM();
objvm.FirstName = "Susheel " + DateTime.Now.Ticks.ToString();
objCommonMthd.SaveData(Usr, "https://localhost:44303/api/UserAPI", ref objOutput);
return View(Usr);
}
Post method in angular 5-
SaveUser(userofrm: NgForm) {
var model = userofrm.value;
var values = JSON.stringify(model);
this.hpclient.post<UserVM>("https://localhost:44321/User/Users/Sush", values).subscribe(success => { }, fail => { });
}
Based on the code that you provided, I did a test with the following simple example, which work well on my side, you can refer to it.
// make request with testing data
var model = {'FirstName':'Test'};
var values = JSON.stringify(model);
console.log(values);
this.hpclient.post<UserVM>("https://localhost:44305/api/data/Sush", values, httpOptions).subscribe(success => { console.log(success);}, fail => { });
Define and configure headers for request(s)
import { HttpClient, HttpHeaders } from '#angular/common/http';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
API controller action
[HttpPost("Sush")]
public IActionResult Sush(UserVM Usr)
{
Usr.UserId = 100;
return Ok(Usr);
}
Test Result
To troubleshoot the issue, you can try to make request with same data from postman etc tool and check if you can get expected data within action method. Or make request with testing data (as I did) rather than value inside userofrm, then check if it can work for you.

Azure Functions OAuth2 from email / password stored in database?

I have a database that contains emails and password hashes.
I would like to secure http trigger's from Azure Functions to allow only authorized call thanks to the Authorization header with a BEARER token.
I think I will need
an http trigger that will generate the token from email/password
Authorize and authenticate the user based on the Authorization header
Can someone get me started on how to create a custom authentication provider or use an existing one and configure Azure Functions to work with it?
Microsoft identity platform supports the OAuth 2.0 Resource Owner Password Credentials (ROPC) grant, which allows an application to sign in the user by directly handling their password.
Get the email(username) and password from database, and send the following request to receive the access token.
POST {tenant}/oauth2/v2.0/token
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=user.read%20openid%20profile%20offline_access
&username=MyUsername#myTenant.com
&password=SuperS3cret
&grant_type=password
You could have look following code snippet, I have tested on azure portal , Azure Function V2:
#r "Newtonsoft.Json"
using Newtonsoft.Json;
using System.Net;
using System.Net.Http.Headers;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
//Parse query parameter
log.LogInformation("C# HTTP trigger function processed a request.");
//Read Request Body
var content = await new StreamReader(req.Body).ReadToEndAsync();
//Extract Request Body and Parse To Class
UserAuthentication objUserInfo = JsonConvert.DeserializeObject<UserAuthentication>(content);
//Message Container
dynamic validationMessage;
//Validate required param
if (string.IsNullOrEmpty(objUserInfo.UserName.Trim()))
{
validationMessage = new OkObjectResult("User name is required!");
return (IActionResult)validationMessage;
}
if (string.IsNullOrEmpty(objUserInfo.Password.Trim()))
{
validationMessage = new OkObjectResult("Password is required!");
return (IActionResult)validationMessage;
}
// Authentication Token Request format
string tokenUrl = $"https://login.microsoftonline.com/common/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = "YourApplicationId",
["client_secret"] = "YourApplicationPassword",
["resource"] = "https://graph.microsoft.com",
["username"] = "" + objUserInfo.UserName + "",
["password"] = "" + objUserInfo.Password + ""
});
// Request For Token Endpoint
using (var _client = new HttpClient())
{
var tokenResponse = await _client.SendAsync(tokenRequest);
AccessTokenClass objAccessToken = JsonConvert.DeserializeObject<AccessTokenClass>(await tokenResponse.Content.ReadAsStringAsync());
// When Token Request Null
if (objAccessToken.access_token == null)
{
validationMessage = new OkObjectResult("Invalid Authentication! Please Check Your Credentials And Try Again!");
return (IActionResult)validationMessage;
}
else
{
return new OkObjectResult(objAccessToken.access_token);
}
}
}
catch (Exception ex)
{
validationMessage = new OkObjectResult("Sorry something went wrong! Please check your given information and try again! {0}" + ex.Message);
return (IActionResult)validationMessage;
}
}
Class I have Used:
UserAuthentication Class
public class UserAuthentication
{
public string UserName { get; set; }
public string Password { get; set; }
}
public class AzureFunctionCreateUserClass
{
public string access_token { get; set; }
public string expires_in { get; set; }
public string token_type { get; set; }
public string resource { get; set; }
}
Note: This an sample for azure portal which I have written on azure function . So try to run on there.
Hope this would help.

WebApi HttpPost body content null

In my WebApi I have a HttpGet and HttpPost method, the get method is working fine and the post method is called but the body content is always null, unless used in a HttpRequestMessage. I tried providing the body content in a string format(preferred datatype) aswell as in a model but neither one of those methods worked. I also tried switching the content type without success. Does anyone know if I'm doing something wrong or how I can easily get the variable data from the HttpRequestMessage, which in the example below is "test".
Method 1:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]string filecontent, string companycode)
{
MessageBox.Show(filecontent);
Return Ok("");
}
Method 2 (with model):
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]ItemXML filecontent, string companycode)
{
MessageBox.Show(filecontent.XMLContent);
Return Ok("");
}
Model:
public class ItemXML
{
public ItemXML(string content)
{
XMLContent = content;
}
public string XMLContent { get; set; }
}
Method 3:
[System.Web.Http.HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem(HttpRequestMessage filecontent, string companycode)
{
var content = filecontent.Content.ReadAsStringAsync().Result;
MessageBox.Show(content);
Return Ok("");
}
Method 3 content string ("test" is the provided value): " content "------WebKitFormBoundarydu7BJizb50runvq0\r\nContent-Disposition: form-data; name=\"filecontent\"\r\n\r\n\"test\"\r\n------WebKitFormBoundarydu7BJizb50runvq0--\r\n" string"
Create a model store data to be sent to server
public class Model {
public string filecontent { get; set;}
public string companycode { get; set;}
}
Update Action
[HttpPost]
[Route("api/v1/AddItem")]
public IHttpActionResult AddItem([FromBody]Model model) {
if(ModelStat.IsValid) {
return Ok(model); //...just for testing
}
return BadRequest();
}
On the client make sure the request is being sent properly. In this case going to use JSON.
public client = new HttpClient();
var model = new {
filecontent = "Hello World",
companycode = "test"
};
var response = await client.PostAsJsonAsync(url, model);
If using another type of client ensure that the data being sent is formatted correctly for the Web API action to accept the request.
Reference Parameter Binding in ASP.NET Web API

Categories

Resources