ASP.NET Core WEB API Problem with GET parameters in API method - asp.net-core

I have this controller
[ApiController]
[Route("api/[controller]")]
public class FamiliesController : ControllerBase
{
readonly FamilyFinanceContext db;
public FamiliesController(FamilyFinanceContext context)
{
db = context;
}
[HttpDelete("deletefamily")]
public async Task<ActionResult<Family>> DeleteFamilly(int id)
{
Family user = db.Families.FirstOrDefault(x => x.Id == id);
if (user == null)
{
return NotFound();
}
db.Families.Remove(user);
await db.SaveChangesAsync();
return Ok(user);
}
}
after call https://localhost:44373/api/families/deletefamily?id=2 i have this error - HTTP ERROR 405
In theory this GET parameters must work. What i done not correctly?

As ESG stated, your trying to do a DELETE, so you need to use the DELETE verb, not the GET verb. You're getting a 405 Method Not Allowed for this reason (you cannot use GET on a DELETE action). You should use a tool like PostMan (https://www.postman.com/) to create your DELETE requests, since you can't really do it easily just in a browser.
To fall more in line with REST convention, you should consider changing your DELETE method slightly:
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteFamily([FromRoute] int id)
{
Family user = db.Families.FirstOrDefault(x => x.Id == id);
if (user == null)
{
return NotFound();
}
db.Families.Remove(user);
await db.SaveChangesAsync();
return NoContent();
}
You would then call this as DELETE https://localhost:44373/api/families/2
By using the [HttpDelete("{id}"] attribute, your moving the id from the query string to the URI, which is more in line with REST convention (the URI represents an object). Query string parameters are more typically used for optional capabilities, such as filtering, sorting, etc and not the endpoint representing the object itself.
Typically DELETE actions do not return content, but that is up to you. If you really want to return the user object, then stick with Ok(user), but NoContent is more typical of the DELETE verb.

Related

PUT, DELETE Not Working (error 404) for ASP.NET core Web API Templates

I have tried several modifications even in .config file to enable those verbs but nothing works.
GET method works fine but 404 for PUT and DELETE.
I already tried removing WebDav Module and other IIS related modifications.
Here is my Controller :
[HttpPut("{id:length(24)}")]
public IActionResult Update(string id, Client clientIn)
{
var client = _clientService.Get(id);
if (client == null)
{
return NotFound();
}
_clientService.Update(id, clientIn);
return NoContent();
}
[HttpDelete("{id:length(24)}")]
[Route("api/Clients/{id}")]
public IActionResult Delete(string id)
{
var client = _clientService.Get(id);
if (client == null)
{
return NotFound();
}
_clientService.Remove(client.ClientId);
return NoContent();
}
I do not know how you set the HttpGet. In this code, because you limit the length of the id, the id must be up to 24 characters to be mapped. You can change it like this.
[HttpPut("{id:maxlength(24)}")]
public IActionResult Update(string id, Client clientIn)
{
//
}
And you can refer to this route constraints.

How to remove value from route int .net core

I'm writing .net core mvc app. I have two methods
[HttpGet("{action}/{userId}")]
public async Task<IActionResult> ChangeUser(string userId)
{
var user = await _dbContext.Users.Where(x => x.Id == userId).FirstOrDefaultAsync();
...
return View(new ChangeUserVM());
}
[HttpPost("{action}/{userId}")]
public async Task<IActionResult> ChangeUser(ChangeUserVM user)
{
...
}
I need routing in the HttpGet method to get into the first method. But then i want to get from the html to the second method and i have to use routing again otherwise i get 405. How i can get rid of routing in the second method?
I can’t verify my suggestion right now, but over second method, try to remove from HttpPost attribute “userId”.
[HttpPost(“action”)]
public async Task<IActionResult> ChangeUser(ChangeUserVM user)

How to authorize subset of resource in .Net Core AuthorizationHandler

I have an implementation of AuthorizationHandler that takes a List<Guid> as its resource. I check each guid to see if the current user has access to this particular resource. My question is, what if I discover they can access some of the guids but not others? Is there some way to reflect this case back to the controller by modifying the following code, or do I need to separately authorize each of the guids in the list?
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement, List<Guid> resource)
{
Guid myId = Guid.Parse(context.User.Claims.FirstOrDefault(c => c.Type == "SomeClaim").Value);
var allAccessableGuids = GetTheGuids(myId);
foreach(var id in resource)
{
if(allAccessableGuids.FirstOrDefault(u => u.Id == id) == null)
{
return Task.CompletedTask;
}
}
context.Succeed(requirement);
return Task.CompletedTask;
}
The code will currently fail the whole list if one guid is found to be unauthorized.
Note that variable names and methods have been changed for simplicity.

Complete request execution from inner method in Web API

I have API that based on ASP.NET Core 2.2 and I want to return result not from my public method that handles request but from an inner method.
public class UsersController : MainController
{
[HttpGet("{id:int}")]
public IActionResult Get(int id)
{
var value = GetSomeValue();
}
private string GetSomeValue()
{
// and here I want to return result to user not even returning result to calling method `Get`
return "";
}
}
I can set status code to response through HttpContext.Response.StatusCode and I know how to set body to the response but I don't know either could I return response to user from GetSomeValue method or not. May be somehow using HttpContext?
I'm not really if this is what you're asking but here are my two cents:
I want to return result not from my public method that handles request but from an inner method.
Well this is not trivial, what you're asking If I get it right is to manipulate the HttpContext so you start the response on an inner method instead of returning from your controller, which is the right way.
I don't know why you would want to do that but, I guess you can get some advice here
In any case I don't get why don't you just:
return Ok(value);
like:
public class UsersController : MainController
{
[HttpGet("{id:int}")]
public IActionResult Get(int id)
{
var value = GetSomeValue();
return Ok(value);
}
private string GetSomeValue()
{
// and here I want to return result to user not even returning result to calling method `Get`
return "";
}
}

Custom error code pages with message

I am trying to create a custom error code page that displays a message I pass to it in my .NET Core MVC 1.1 application. I setup custom error code pages support in the Startup.cs class file and then created a simple view in a controller that does public IActionResult Example1 => NotFound("Some custom error message"). I expected this message to be pushed to the controller however this is not the case. Calling NotFound() without any parameters hits the error controller but as soon as I pass a message through, the controller is never used and a simple text message is displayed.
I could have sworn I used to do this in the past with classic .NET MVC but it has been awhile.
How can I have custom error code pages that display the proper error. I also need the ability in a controller to return the standard text or JSON response during the error for cases when I expect a JSON response (API actions and such). I am assuming there is a way to do this with a attribute but I have yet to find a way to do either of these tasks.
What you could do is something similar to how the StatusCodePages middleware works. That middleware allows a pipeline re-execution model, to allow handling status code errors through the normal MVC pipeline. So when you return a non-successful status code from MVC, the middleware detects that and then re-executes the whole pipeline for a status code error route. That way, you are able to fully design status code errors. But as Chris Pratt already mentioned, those status codes are typically limited to just their code. There is not really a way to add additional details to it.
But what we could do is create our own error handling implementation on top of that re-execution model. For that, we create a CustomErrorResponseMiddleware which basically checks for CustomErrorResponseException exceptions and then re-executes the middleware pipeline for our error handler.
// Custom exceptions that can be thrown within the middleware
public class CustomErrorResponseException : Exception
{
public int StatusCode { get; set; }
public CustomErrorResponseException(string message, int statusCode)
: base(message)
{
StatusCode = statusCode;
}
}
public class NotFoundResponseException : CustomErrorResponseException
{
public NotFoundResponseException(string message)
: base(message, 404)
{ }
}
// Custom context feature, to store information from the exception
public interface ICustomErrorResponseFeature
{
int StatusCode { get; set; }
string StatusMessage { get; set; }
}
public class CustomErrorResponseFeature : ICustomErrorResponseFeature
{
public int StatusCode { get; set; }
public string StatusMessage { get; set; }
}
// Middleware implementation
public class CustomErrorResponseMiddleware
{
private readonly RequestDelegate _next;
private readonly string _requestPath;
public CustomErrorResponseMiddleware(RequestDelegate next, string requestPath)
{
_next = next;
_requestPath = requestPath;
}
public async Task Invoke(HttpContext context)
{
try
{
// run the pipeline normally
await _next(context);
}
catch (CustomErrorResponseException ex)
{
// store error information to be retrieved in the custom handler
context.Features.Set<ICustomErrorResponseFeature>(new CustomErrorResponseFeature
{
StatusCode = ex.StatusCode,
StatusMessage = ex.Message,
});
// backup original request data
var originalPath = context.Request.Path;
var originalQueryString = context.Request.QueryString;
// set new request data for re-execution
context.Request.Path = _requestPath;
context.Request.QueryString = QueryString.Empty;
try
{
// re-execute middleware pipeline
await _next(context);
}
finally
{
// restore original request data
context.Request.Path = originalPath;
context.Request.QueryString = originalQueryString;
}
}
}
}
Now, all we need to do is hook that up. So we add the middleware within our Startup.Configure, somewhere near the beginning:
app.UseMiddleware<CustomErrorResponseMiddleware>("/custom-error-response");
The /custom-error-response is the route that we are re-executing when a custom response is being requested. This can be a normal MVC controller action:
[Route("/custom-error-response")]
public IActionResult CustomErrorResponse()
{
var customErrorResponseFeature = HttpContext.Features.Get<ICustomErrorResponseFeature>();
var view = View(customErrorResponseFeature);
view.StatusCode = customErrorResponseFeature.StatusCode;
return view;
}
Since this uses MVC, this also needs a view:
#model ICustomErrorResponseFeature
#{
ViewData["Title"] = "Error";
}
<p>There was an error with your request:</p>
<p>#Model.StatusMessage</p>
And that’s basically all. Now, we can just throw our custom error response exceptions from our MVC actions to trigger this:
// generate a 404
throw new NotFoundResponseException("This item could not be found");
// or completely custom
throw new CustomErrorResponseException("This did not work", 400);
Of course, we could also expand this further, but that should be the basic idea.
If you are already using the StatusCodePages middleware, you might think whether all this custom re-execution is really necessary, when you already have exactly that in the StatusCodePages middleware. And well, it is not. We can also just expand on that directly.
For that, we will just add the context features, which we can set at any point during the normal execution. Then, we just return a status code, and let the StatusCodePages middleware run. Inside its handler, we can then look for our feature and use the information there to expand the status code error page:
// Custom context feature
public interface IStatusCodePagesInfoFeature
{
string StatusMessage { get; set; }
}
public class StatusCodePagesInfoFeature : IStatusCodePagesInfoFeature
{
public string StatusMessage { get; set; }
}
// registration of the StatusCodePages middleware inside Startup.Configure
app.UseStatusCodePagesWithReExecute("/error/{0}");
// and the MVC action for that URL
[Route("/error/{code}")]
public IActionResult StatusCode(int code)
{
var statusCodePagesInfoFeature = HttpContext.Features.Get<IStatusCodePagesInfoFeature>();
return View(model: statusCodePagesInfoFeature?.StatusMessage);
}
Inside of the normal controller actions, we can set that feature before returning a status code:
HttpContext.Features.Set<IStatusCodePagesInfoFeature>(new StatusCodePagesInfoFeature
{
StatusMessage = "This item could not be found"
});
return NotFound();
It is too bad you cannot intercept NotFound, Unauthorized, etc. responses in a middleware class.
Okay, option three! You can totally intercept those responses, just not inside of middleware, since these are MVC results and will not leave the MVC pipeline. So you have to intercept them within the MVC filter pipeline. But we could absolutely run a filter, for example a result filter, that modifies the result.
The problem is that we still need a way to pass the information on. We could use a context feature again, but we can also use the MVC object results. So the idea is that we can just do the following in the MVC actions:
return NotFound("The item was not found");
So usually, that string would be the plain text response. But before the result is being executed and the response is being generated, we can run a result filter to modify this and return a view result instead.
public class StatusCodeResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// retrieve a typed controller, so we can reuse its data
if (context.Controller is Controller controller)
{
// intercept the NotFoundObjectResult
if (context.Result is NotFoundObjectResult notFoundResult)
{
// set the model, or other view data
controller.ViewData.Model = notFoundResult.Value;
// replace the result by a view result
context.Result = new ViewResult()
{
StatusCode = 404,
ViewName = "Views/Errors/NotFound.cshtml",
ViewData = controller.ViewData,
TempData = controller.TempData,
};
}
// intercept other results here…
}
await next();
}
}
All you need is a view at Views/Errors/NotFound.cshtml now and everything will magically work once you have the filter registered.
You can either register the filter by adding a [TypeFilter(typeof(StatusCodeResultFilter))] attribute to the controller or individual actions, or you can register it globally.
What you want is not possible. When you do something like return NotFound with a message, that message will be included in the response body only if it's left unmolested. When you do something like enable status code pages, the NotFound is simply caught by the middleware, and the request will simply be handed off to your error handling action to ultimately obtain the response. Importantly, that means your original NotFoundResult along with any custom message has been round-filed.