WebApi2 Routing with Get Request does not work - asp.net-web-api2

Hi I'm making a simple program that you can search for the user, register the user, and get all the user that are registered.
I want to be able to search for my users using
/api/user/search?name=...&email= B
But this is not working. I get an error that is:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:63881/api/user/search'.",
"MessageDetail": "No action was found on the controller 'User' that matches the request."
}
I have code that looks like this
[RoutePrefix("api/user")]
public class UserController : ApiController
{
public HttpResponseMessage Get() {
// Check if the user is in the database
return Request.CreateResponse(HttpStatusCode.OK, "Return All");
}
public HttpResponseMessage Post(UserForm form) {
if (ModelState.IsValid == false) {
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
if (form == null) {
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Please enter data");
}
// Register the person
return Request.CreateResponse(HttpStatusCode.OK, "Register");
}
[Route("search/{name}/{email}")]
[HttpGet]
public HttpResponseMessage SearchGet(string name, string email) {
return Request.CreateResponse(HttpStatusCode.OK, "Found");
}
Thanks!

Adding another route attribute to the SearchGet method should work:
[Route("search") ]
[Route("search/{name}/{email}")]
[HttpGet]
public HttpResponseMessage SearchGet(string name, string email) {
return Request.CreateResponse(HttpStatusCode.OK, "Found");
}
It will map the request based on the querystring parameter count and name or each querystring parameter.

Related

What return in web api asp net core

I'm not sure what I should return in LoginAsync method. Sometimes I have an error to return, but sometimes authentication succes. I'm currently using dynamic, but heard it's bad practice. What should i use?
It's LoginAsync in AccountService service:
public async Task<dynamic> LoginAsync(User user)
{
var existingUser_byEmail = await FindUserByEmailAsync(user.Email);
if (existingUser_byEmail == default)
return new Error
{
StatusCode = 400,
ErrorMessages = { { "email", "Nie odnaleziono użytkownika z podanym adresem e-mail" } }
};
if (BCrypt.Net.BCrypt.EnhancedVerify(user.Password, existingUser_byEmail.Password))
return new AuthSuccessful { StatusCode = 200, Token = _jwtService.GenerateToken(existingUser_byEmail) };
else
return new Error { StatusCode = 401, ErrorMessages = { { "password", "Błędne hasło" } } };
}
And it's Login method in AccountController:
[HttpPost("login")]
public async Task<IActionResult> LogIn([FromBody] User user)
{
var response = await _accountService.LoginAsync(user);
return StatusCode(response.StatusCode, response);
}
Thanks for all answers, have a nice day! :D
You have multiple options.
1. Interface/base class
It seems everything you return has a similar structure – a StatusCode property, and some other additional properties that make sense in the context of the given status code.
So the most obvious might be to create a base class or an interface for these, like this:
public interface IOperationResult
{
int StatusCode { get; init; }
object Response { get; }
}
public class Error : IOperationResult
{
public int StatusCode { get; init; }
public string[,] ErrorMessages { get; init; }
public object Response => ErrorMessages;
}
public class AuthSuccessful : IOperationResult
{
public int StatusCode { get; init; }
public string Token { get; init; }
public object Response => Token;
}
This is a well-defined structure that will arguably support more complex business logic, when you might have to check the exact type of the return value and access properties on them in a type-safe manner.
2. Value tuples
Another option that I use a lot these days is returning a value tuple, with one member containing the success/failure, and another the result; like the following. It looks pretty bad in this case, because the format of the error messages aren't defined. But if you used a class or struct for that, it would be quite okay.
public async Task<(int statusCode, object response)> LoginAsync(User user)
{
var existingUser_byEmail = await FindUserByEmailAsync(user.Email);
if (existingUser_byEmail == default)
return (statusCode: 400, response: new[] { new[] { "email", "Nie odnaleziono użytkownika z podanym adresem e-mail" } });
if (BCrypt.Net.BCrypt.EnhancedVerify(user.Password, existingUser_byEmail.Password))
return (statusCode: 200, response: _jwtService.GenerateToken(existingUser_byEmail));
else
return (statusCode: 401, response: new[] { new[] { "password", "Błędne hasło" } });
}
// Then you can do a tuple deconstruction assignment:
[HttpPost("login")]
public async Task<IActionResult> LogIn([FromBody] User user)
{
var (statusCode, response) = await _accountService.LoginAsync(user);
return StatusCode(statusCode, response);
}
3. Do the HTTP code and error message selection outside the service
It's more traditional to return a different flag from authentication services, and then map that to an HTTP code somewhere closer to the controller (or inside the controller). This way you avoid coupling the service to HTTP concerns, which arguably shouldn't be their responsibility.
For example a lot of built-in Identity services use the Microsoft.AspNetCore.Identity.SignInResult class.
In the following implementation I changed the LoginAsync method to return a failed result both in the case of invalid password and invalid email. This is actually a better practice, because if you tell the person trying to log in that an email address does or doesn't have an account, you're leaking out user information.
public async Task<(SignInResult result, string token)> LoginAsync(User user)
{
var existingUser_byEmail = await FindUserByEmailAsync(user.Email);
if (existingUser_byEmail == default)
return (SignInResult.Failed, null);
if (BCrypt.Net.BCrypt.EnhancedVerify(user.Password, existingUser_byEmail.Password))
return (SignInResult.Success, _jwtService.GenerateToken(existingUser_byEmail));
else
return (SignInResult.Failed, null);
}
[HttpPost("login")]
public async Task<IActionResult> LogIn([FromBody] User user)
{
var (result, token) = await _accountService.LoginAsync(user);
if (result.Succeeded)
return Ok(token);
// Handle lock-out and 'login not allowed' situation too, if necessary.
return Unauthorized("Invalid password or email.");
}

asp.net core 'No route matches the supplied values' with controller-level route values

The following controller throws a System.InvalidOperationException: No route matches the supplied values exception when posting a new item (by the CreatedAtRoute method):
namespace MyApp.Controllers
{
[ApiController]
[Route("api/offices/{officeId}/[controller]")]
public class ResourcesController : ControllerBase
{
/* ... */
[HttpGet]
public async Task<IActionResult> GetAsync(Guid officeId) =>
this.Ok(await this.client.GetResourcesAsync(officeId));
[HttpGet("{id}", Name = "GetResourceById")]
public async Task<IActionResult> GetAsync(Guid officeId, string id)
{
var resource = await this.client.GetResourceAsync(officeId, id);
return resource == null ? (IActionResult) this.NotFound() : this.Ok(resource);
}
[HttpPost]
public async Task<IActionResult> PostAsync(Guid officeId, Resource resource)
{
try
{
var result = await this.client.CreateResourceAsync(officeId, resource);
return this.CreatedAtRoute("GetResourceById", new {officeId, id = resource.Id}, result);
}
catch (Exception e)
{
this.logger.LogError(e, "Error while creating a resource");
return this.StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
}
Is this a bug or is there another way to use route values specified at controller level?
Tested on asp.net core 3 (preview 8).
The Id value of the resource was null, and therefore the anonymous object passed to the method was missing the id value, which is not optional on the route template and therefore the route was not found.
Using result.Id solved it, because the client was assigning the id (generated by the MongoDb instance behind).
I think its because of the parameters that you gave.
In this line you are giving 2 anonymus objects params to your root, but your root contains and asks for 1 parameter.
return this.CreatedAtRoute("GetResourceById", new {officeId, id = resource.Id}, result);
EDIT
I think the {id} parameter makes a conflict over your root parameters.
[Route("api/offices/{officeId}/[controller]")]
[HttpGet("{id}", Name = "GetResourceById")]
public async Task<IActionResult> GetAsync(Guid officeId, string id)
{
var resource = await this.client.GetResourceAsync(officeId, id);
return resource == null ? (IActionResult) this.NotFound() : this.Ok(resource);
}

How to validate for illegal fields in Model Validation

I have a .NET Core 2.2 web-api that accepts a PersonDto, it is getting validated with Model Validation, but it does not check for illegal fields. It only checks if matching fields are valid.
I want to make sure that the supplied JSON contains only the fields that are in my Dto (Class).
public class PersonDto
{
public string firstname { get; set; }
public string lastname { get; set; }
}
My controller looks simplified like this:
public async Task<ActionResult<Person>> Post([FromBody] PersonDto personDto)
{
// do things
}
I send it incorrect fields (name does not exist in my dto) and the ModelState is valid.
{
"name": "Diego"
}
I expected the Model Validation to complain that the field "Name" does not exist.
How can I check for illegal fields?
You could use ActionFilter and Reflection to compare the request body content to the model fields. If there are unexpected fields, manually add model errors and the ModelState.IsValid will be false.
1.Create an ActionFilter
public class CompareFieldsActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//get all fields name
var listOfFieldNames = typeof(PersonDto).GetProperties().Select(f => f.Name).ToList();
var request = context.HttpContext.Request;
request.Body.Position = 0;
using (var reader = new StreamReader(request.Body))
{
//get request body content
var bodyString = reader.ReadToEnd();
//transfer content to json
JObject json = JObject.Parse(bodyString);
//if json contains fields that do not exist in Model, add model error
foreach (JProperty property in json.Children())
{
if (!listOfFieldNames.Contains(property.Name))
{
context.ModelState.AddModelError("Filed", "Field does not exist");
}
}
}
base.OnActionExecuting(context);
}
}
2.Use the filter on your action:
[HttpPost]
[CompareFieldsActionFilter]
public async Task<ActionResult<Person>> Post([FromBody] PersonDto personDto)
{
if (ModelState.IsValid)
{
// do things
}
// do things
}

Web API Post Method not working

I have a webapi controller and following is a post method.
public HttpResponseMessage Register(string email, string password)
{
}
How do I test from the browser?
When I test it from the browser with the following , it is not hitting the controller.
http://localhost:50435/api/SignUp/?email=sini#gmail.com&password=sini#1234
It is giving me the below error.
Can't bind multiple parameters ('id' and 'password') to the request's
content.
Can you please help me???
You get an error, because you cannot pass multiple parameter to WebApi in this way.
First option:
You can create a class and pass data through from body in this way:
public class Foo
{
public string email {get;set;}
public string password {get;set;}
}
public HttpResponseMessage Register([FromBody] Foo foo)
{
//do something
return Ok();
}
Second Option:
public HttpResponseMessage Register([FromBody]dynamic value)
{
string email= value.email.ToString();
string password = value.password.ToString();
}
And pass json data in this way:
{
"email":"abc#test.com",
"password":"123#123"
}
Update:
If you wan to get data from URL, then you can use Attribute Routing.
[Route("api/{controller}/{email}/{password}")]
public HttpResponseMessage Register(string email, string password)
{
//do something
return Ok();
}
Note:
URL should be : http://localhost:50435/api/SignUp/sini#gmail.com/sini#1234
Don't forget to enable attribute routing in WebApiConfig
If you use this way, you will have security issue.

Redirect to action with parameters always null in mvc

When I tried redirect to action, the parameter is always null when I received ? I don't know why this happening like these.
ActionResult action1() {
if(ModelState.IsValid) {
// Here user object with updated data
redirectToAction("action2", new{ user = user });
}
return view(Model);
}
ActionResult action2(User user) {
// user object here always null when control comes to action 2
return view(user);
}
And with this I've another doubt. when I accessed action with route, i can get values only by RouteData.Values["Id"]. the values routed doesn't send to parameter.
<a href="#Url.RouteUrl("RouteToAction", new { Id = "454" }> </a>
Here Am I miss any configure ? or anything I miss.
ActionResult tempAction(Id) {
// Here Id always null or empty..
// I can get data only by RouteData.Values["Id"]
}
You cannot pass complex objects in an url like that. You will have to send its constituent parts:
public ActionResult Action1()
{
if (ModelState.IsValid)
{
// Here user object with updated data
return RedirectToAction("action2", new {
id = user.Id,
firstName = user.FirstName,
lastName = user.LastName,
...
});
}
return view(Model);
}
Also notice that I have added the return RedirectToAction instead of only calling RedirectToAction as shown in your code.
But a much better approach is to send only the id of the user:
public ActionResult Action1()
{
if (ModelState.IsValid)
{
// Here user object with updated data
return RedirectToAction("action2", new {
id = user.Id,
});
}
return view(Model);
}
and in your target action use this id to retrieve the user from wherever this user is stored (could be database or something):
public ActionResult Action2(int id)
{
User user = GetUserFromSomeWhere(id);
return view(user);
}
Some alternative approaches (but one I don't recommend or use) is to persist the object in TempData:
public ActionResult Action1()
{
if(ModelState.IsValid)
{
TempData["user"] = user;
// Here user object with updated data
return RedirectToAction("action2");
}
return view(Model);
}
and in your target action:
public ActionResult Action2()
{
User user = (User)TempData["user"];
return View(user);
}