I have a controllerclass called "GebruikersController"
when I use "https://localhost:5001/Gebruikers" I get the right output but when I use "https://localhost:5001/api/GebruikerController/Gebruikers" (how it should work?) I get a blank page. Can anybody help me? Thanks!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using RESTAPI.Data;
using RESTAPI.Data.Repositories;
using RESTAPI.DTOs;
using RESTAPI.Models;
namespace RESTAPI.Controllers
{
[ApiConventionType(typeof(DefaultApiConventions))]
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class GebruikersController : ControllerBase
{
private readonly IGebruikerRepository _gebruikerRepository;
public GebruikersController(IGebruikerRepository context)
{
_gebruikerRepository = context;
}
// GET: api/Gebruikers
/// <summary>
/// Geeft alle gebruikers geordend op achternaam
/// </summary>
/// <returns>array van gebruikers</returns>
[HttpGet("/Gebruikers")]
public IEnumerable<Gebruiker> GetGebruikers()
{
return _gebruikerRepository.GetAlleGebruikers().OrderBy(d => d.Achternaam);
}
// GET: api/Gebruikers/5
/// <summary>
/// Geeft de gebruiker met het gegeven id terug
/// </summary>
/// <param name="id">het id van de gebruiker</param>
/// <returns>De gebruiker</returns>
[HttpGet("{id}")]
public ActionResult<Gebruiker> GetGebruiker(int id)
{
Gebruiker gebruiker = _gebruikerRepository.GetBy(id);
if (gebruiker == null) return NotFound();
return gebruiker;
}
}
}
Try to use
https://localhost:5001/api/Gebruikers/Gebruikers
instead of
https://localhost:5001/api/GebruikerController/Gebruikers.
The "controller" word is not written in the URL.
Add a Route attribute on the action. then, both routes will be recognized.
[HttpGet("/Gebruikers")]
[Route("Gebruikers")]
public IActionResult GetGebruikers()
{
return Ok();
}
The slash will ignore the route attribute on your controller, so it can only recognize the route: "https://localhost:5001/Gebruikers"
Related
Hello I would like to seek advice or recommendation on how to add a authentication token on swagger ui without converting my project into asp.net core since I would be only using asp.net. I have check some guides on the internet with JWT or similar to that but it's framework is on asp.net core.
I have added a sample controller below and also a sample goal that on my mind this is from a asp.net core.
using System.Web.Http;
using Newtonsoft.Json.Linq;
using ABSRestService.QFlowFFP;
using System.Configuration;
using System;
using System.Net;
using ABSRestService.Models;
using Swashbuckle.Swagger.Annotations;
namespace ABSRestService.Controllers
{
[RoutePrefix("api/extensions")]
public class AppointmentTypeController : ApiController
{
/// <summary>
/// </summary>
/// <remarks>
/// This API will provide all the Enabled Appointment Type
/// Sample request:
/// GET /AppointmentType
/// </remarks>
/// <returns>
/// All the Enabled Appointment Type
/// </returns>
[HttpGet]
[Route("AppointmentType")]
[SwaggerResponse(200, type: typeof(AppointmentTypeResponse), description: "List of one enabled Appointment Type")]
[SwaggerResponse(500, type: typeof(InternalServerResponse), description: "When exception occurs")]
public JObject AppointmentType()
{
try
{
string websiteAppointmentTypeNameCtName = ConfigurationManager.AppSettings["WebsiteAppointmentTypeNameCTName"];
string websiteAppointmentTypeFlagCtName = ConfigurationManager.AppSettings["WebsiteAppointmentTypeFlagCTName"];
string absAptDisplayOrder = ConfigurationManager.AppSettings["AbsAptDisplayOrder"];
string appointmentTypeDesc = ConfigurationManager.AppSettings["AppointmentTypeDesc"];
string showAppTypeInOne = ConfigurationManager.AppSettings["ShowAppTypeInOne"];
string languageCode = ConfigurationManager.AppSettings["LanguageCode"];
QFlowFFPClient client = new QFlowFFPClient();
AppointmentType1[] appointmentType = client.GetAllOneAppointmentTypes(
websiteAppointmentTypeFlagCtName,
websiteAppointmentTypeNameCtName,
absAptDisplayOrder,
appointmentTypeDesc,
showAppTypeOne,
languageCode);
return JObject.FromObject(new
{
status = HttpStatusCode.OK,
response = appointmentType
});
}
catch (Exception ex)
{
return JObject.FromObject(new
{
status = HttpStatusCode.InternalServerError,
response = ex.Message
});
}
}
}
}
Currently my code looks something like this:
public class UserController : ControllerBase
{
[HttpPost]
public async Task<ActionResult> Login(LoginCredentialsModel model)
{
if (someValidLogic){
return Ok(new { message = "User login success.",
additionalParameters = new {
param1 = "",
param2 = ""
}
});
}
else {
return BadRequest(new { errorMessage = "Username or password is incorrect.",
additionalParameters = {
StatusCode = 400,
RetryLeftCount = 3 }
});
}
}
}
I am manually creating JSON objects to return to UI in every endpoint so that I can have consistent communication and allow UI to handle messages at a global level. (using angular interceptors in my case). I am wanting to create a custom class that can be implemented by all controllers that have only two options of return types - Success(), Fail().
public class UserController : CustomControllerBase
{
[HttpPost]
public async Task<ActionResult> Login(LoginCredentialsModel model)
{
if (someValidLogic){
return Success("User login success.", additionalParameters)
}
else {
return Error("Username or password is incorrect.", additionalParameters);
}
}
}
And my CustomControllerBase would be in charge of formatting the results in the proper form. I know I can do this via middleware but I really do not want to as it still allows developers to accidentally send back some non-valid results and middleware not knowing how to handle it.
Even if you make a custom base controller you're going to have to make the custom base controller extend Microsoft.AspNetCore.Mvc.ControllerBase, else you won't get all the automatic routing, the [HttpPost] type attributes etc. And once you extend ControllerBase, all the methods like Ok() will be available to your other developers. I'd really just try to communicate with the developers to not use Ok() etc.
This is hacky, but you could "block" the other methods by overriding them in your CustomControllerBase and just throwing an exception. This won't create a compile time error but it will at least create a shallow runtime exception. Also there's a LOT of these methods and you'd have to override them all.
public class CustomControllerBase : ControllerBase
{
public ActionResult Success(string message)
{
return base.Ok(message);
}
public new ActionResult Ok(object value)
{
throw new Exception("don't use this");
}
}
public class UserController : CustomControllerBase
{
public async Task<ActionResult> Hello()
{
return Ok("hello"); // throws runtime exception
}
}
Alternatively use a style checker and outlaw all uses of Ok() and similar methods. You'd also have to disallow lines like return new OkObjectResult("hello"); It's really going to be quite an ordeal to get this right.
As you've requested, to have a custom class that you can re-use in returning response messages back to the calling request, I created a custom class that inherits from ActionResult that we can return.
After having this class in place, we are going to use it to create a base/custom controller where we will add our Success and Fail methods which any controller can inherit from to use the extended methods.
CustomActionResult
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text
/// <summary>
/// Customized <see cref="ActionResult"/> that allows easily setting <see cref="HttpStatusCode"/>
/// and data result object to be returned for a request.
/// </summary>
public class CustomActionResult : ActionResult
{
private static UTF8Encoding utf = new UTF8Encoding();
/// <summary>
/// Http response code.
/// </summary>
public HttpStatusCode StatusCode { get; set; }
/// <summary>
/// Data to return back to the user as an <see cref="object"/> of any <see cref="Type"/>
/// </summary>
public object Data { get; set; }
/// <summary>
/// Parameterless contructor that initializes the ActionResult with
/// <see cref="HttpStatusCode.OK"/> as the default Response Code.
/// </summary>
public CustomActionResult()
{
StatusCode = HttpStatusCode.OK;
Headers = new Dictionary<string, string>();
}
/// <summary>
/// Constructor that initializes the ActionResult with a specified <see cref="HttpStatusCode"/>
/// </summary>
/// <param name="statusCode">
/// Http response code to set for this ActionResult.
/// </param>
public CustomActionResult(HttpStatusCode statusCode)
:this()
{
StatusCode = statusCode;
}
/// <summary>
/// Constructor that initializes the ActionResult with a specified <see cref="HttpStatusCode"/>
/// </summary>
/// <param name="statusCode">
/// Http response code to set for this ActionResult.
/// </param>
/// <param name="message">Reason phrase</param>
public CustomActionResult(HttpStatusCode statusCode, string message)
:this()
{
StatusCode = statusCode;
Data = message;
}
private string Json
{
get
{
if(Data != null)
{
if (Data.GetType() == typeof(string))
{
return Data.ToString();
}
return JsonConvert.SerializeObject(Data);
}
return string.Empty;
}
}
public byte[] GetBuffer() => utf.GetBytes(Json);
public Dictionary<string,string> Headers { get; private set; }
public override void ExecuteResult(ActionContext context)
{
if (Headers.Count > 0)
{
for (int i = 0; i < Headers.Count; i++)
{
var item = Headers.ElementAt(i);
context.HttpContext.Response.Headers.Add(item.Key, item.Value);
}
}
if (!string.IsNullOrWhiteSpace(Json))
{
context.HttpContext.Response.ContentType = "application/json";
}
context.HttpContext.Response.StatusCode = (int)StatusCode;
context.HttpContext.Response.Body.Write(GetBuffer(), 0, GetBuffer().Length);
}
}
CustomBaseController
public class CustomBaseController : ControllerBase
{
public ActionResult Success(object value)
{
return new CustomActionResult(){ Data = value };
}
public ActionResult Success(string message, params object[] additionalParams)
{
if(additionalParams.Length > 0){
return new CustomActionResult(){
Data = new { message, additionalParams }
};
}else{
return new CustomActionResult() { Data = message };
}
}
public ActionResult Fail(object value)
{
return new CustomActionResult(HttpStatusCode.BadRequest){ Data = value };
}
public ActionResult Fail(string message, params object[] additionalParams)
{
if(additionalParams.Length > 0){
return new CustomActionResult(HttpStatusCode.BadRequest){
Data = new { ErrorMessage = message, additionalParams }
};
}else{
return new CustomActionResult(HttpStatusCode.BadRequest){
Data = new { ErrorMessage = message }
};
}
}
}
Usage
public class UserController : CustomBaseController
{
[HttpPost]
public async Task<ActionResult> Login(LoginCredentialsModel model)
{
if(!ModelState.IsValid)
return this.Fail(ModelState);
// add your other custom logic here
if(someLogic){
return this.Success("your-message", additionalParams);
} else {
return this.Fail("custom-error-message", additionalParams);
}
}
}
We have a couple of ApiController implementations and we do not want most operations to be included in the metadata of ApiExplorer.
By default, if you do not add [ApiExplorerSettings(IgnoreApi = true)] to your operation, it will be added so this means the default is false.
This probably due to IgnoreApi being a boolean and defaulting to false but how can I change this default to true without having to override ApiExplorerSettings?
This is a basic WebApi implementation without using MVC components.
I tried looking around for simple config based solutions or examples of ApiExplorerSettings usage but none have really worked out for me.
The closest to what I want is: DotNetCore - is ApiExplorer supported, and how to use it?; however, it focuses on MVC.
// For example
[RoutePrefix("api/test")]
public class TestController : ApiController
{
[HttpGet]
[Route("helloworld")]
[ApiExplorerSettings(IgnoreApi = false)]
public string HelloWorld() {
return "Hello world!";
}
[HttpGet]
[Route("goodbyeworld")]
[ApiExplorerSettings(IgnoreApi = true)]
public string HelloWorld() {
return "Goodbye world!";
}
[HttpGet]
[Route("hiworld")]
[ApiExplorerSettings(IgnoreApi = true)]
public string HelloWorld() {
return "Hi world!";
}
[HttpGet]
[Route("seeyaworld")]
[ApiExplorerSettings(IgnoreApi = true)]
public string HelloWorld() {
return "See ya world!";
}
}
I want to be able to just use ApiExplorerSettings on operations which I want to use instead of marking the ones I do not want to use.
For those who are interested, I ended up overriding the ApiExplorer class to override the ShouldExploreAction and ShouldExploreController methods. I reversed the boolean logic there and it works as requested.
[Edited with example as this was requested]
You can do the following:
Make a class that overrides from ApiExplorer
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
using System.Web.Http.Routing;
namespace WebApi.Api
{
public class CustomApiExplorer : ApiExplorer
{
public CustomApiExplorer(HttpConfiguration configuration) : base(configuration)
{
}
/// <summary>
/// Determines whether the controller should be considered.
/// </summary>
/// <param name="controllerVariableValue">The controller route parameter value.</param>
/// <param name="controllerDescriptor">The associated <see cref="HttpControllerDescriptor">controller descriptor</see>.</param>
/// <param name="route">The associated <see cref="IHttpRoute">route</see>.</param>
/// <returns>True if the controller should be explored; otherwise, false.</returns>
public override bool ShouldExploreController(string controllerVariableValue, HttpControllerDescriptor controllerDescriptor, IHttpRoute route)
{
if (string.IsNullOrEmpty(controllerVariableValue) || controllerDescriptor == null || route == null)
{
throw new ArgumentException();
}
var setting = controllerDescriptor.GetCustomAttributes<ApiExplorerSettingsAttribute>().FirstOrDefault();
// Basically you check if there is a setting used and if ignore is set to true or false. You can also check if the routing is as one would expect but that is a different discussion. With this the ApiExplorer changes its logic by only registering API's that actively state IgnoreApi = false.
if (setting != null && !setting.IgnoreApi)
{
return true;
}
return false;
}
}
}
Use custom class in WebApiConfig
In the WebApiConfig.cs file you can overide the IApiExplorer service instance by placing the following line in the Register(HttpConfiguration config) method.
public static void Register(HttpConfiguration config) {
...
config.Services.Replace(typeof(IApiExplorer), new CustomApiExplorer(config));
...
}
you can the IDocumentFilter interface to achieve it:
public class ApiDocFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var pathsToRemove = swaggerDoc.Paths
.Where(pathItem => !pathItem.Key.Contains("/api/"))
.ToList();
foreach (var item in pathsToRemove)
{
swaggerDoc.Paths.Remove(item.Key);
}
}
}
in Startup.cs -> ConfigureServices use the filter:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {Title = "some API", Version = "v1"});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.DocumentFilter<ApiDocFilter>();//<-- use doc filter
});
I have a mvc application. And I am using a API for retrieving. teachers.
This is the class:
[Authorize(Roles = IdentityRoles.Teacher)]
[RoutePrefix("api//current")]
public sealed class CurrentTeacherController : ApiControllerBase
{
private readonly ICurrentTeacherProcess _currentTeacherProcess;
/// <summary>
/// Constructor.
/// </summary>
public CurrentTeacherController(ICurrentTeacherProcess process)
{
_currentTeacherProcess = process;
}
/// <summary>
/// Gets the teacher data of the current user
/// </summary>
/// <returns>The TeacherDto of the current teacher</returns>
[Route("")]
[HttpGet]
public TeacherDto GetTeacher()
{
return _currentTeacherProcess.GetTeacher();
}
}
And I am using Postman for retriving a teacher. For example teacher with id: 1001.
And In Postman I put this:
http://localhost:6598/api/register/
But I get a : 404 not found exception.
This is the startup.cs file:
public sealed class Startup
{
/// <summary>
/// Configures the application for use with OWIN. This method is called implicitly by Microsoft.Owin.Host.SystemWeb.
/// </summary>
/// <param name="app"><see cref="IAppBuilder" />implementation.</param>
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
WebApiConfig.Register(config);
var container = AutofacConfig.Register(config);
app.UseAutofacLifetimeScopeInjector(container);
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationType = "Hallo",
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = ApiGatewaySecurityTokenHandler.CreateTokenValidationParameters(
"hoi", "hoi", IdentityRoles.Duo, IdentityRoles.Competent, IdentityRoles.Berichtenbox),
TokenHandler = container.Resolve<JwtSecurityTokenHandler>()
});
app.UseStageMarker(PipelineStage.PostAuthenticate);
app.UseMiddlewareFromContainer<ApiGatewayMiddleware>();
app.UseAutofacWebApi(config);
app.UseWebApi(config);
}
}
update with startup.cs file
You get a 404 because a handler doesn't exist in your API for that request. You need to add the id to your API route like so
[Route("{id}")]
[HttpGet]
public TeacherDto GetTeacher(int id/* or other type if you aren't using int as your primary key*/ )
{
return _currentTeacherProcess.GetTeacher(id);
}
And your request should be:
http://localhost:6598/api/register/teachers/current/1001
I am currently working on authorizing calls to my API controllers. I am following this tutorial: http://www.c-sharpcorner.com/article/asp-net-mvc5-rest-web-api-authorization/
WebApiController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace WebApiAuthorization.Controllers {
[Authorize]
public class WebApiController: ApiController {
// GET api/values
public IEnumerable < string > Get() {
return new string[] {
"Hello REST API",
"I am Authorized"
};
}
// GET api/values/5
public string Get(int id) {
return "Hello Authorized API with ID = " + id;
}
// POST api/values
public void Post([FromBody] string value) {}
// PUT api/values/5
public void Put(int id, [FromBody] string value) {}
// DELETE api/values/5
public void Delete(int id) {}
}
}
AuthorizationHeaderHandler.cs
namespace WebApiAuthorization.Helper_Code.Common {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using WebApiAuthorization.Resources.Constants;
/// <summary>
/// Authorization for web API class.
/// </summary>
public class AuthorizationHeaderHandler: DelegatingHandler {#
region Send method.
/// <summary>
/// Send method.
/// </summary>
/// <param name="request">Request parameter</param>
/// <param name="cancellationToken">Cancellation token parameter</param>
/// <returns>Return HTTP response.</returns>
protected override Task < HttpResponseMessage > SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
// Initialization.
IEnumerable < string > apiKeyHeaderValues = null;
AuthenticationHeaderValue authorization = request.Headers.Authorization; //This is always null even as I put [Authorize] tag above my controller
string userName = null;
string password = null;
// Verification.
if (request.Headers.TryGetValues(ApiInfo.API_KEY_HEADER, out apiKeyHeaderValues) && !string.IsNullOrEmpty(authorization.Parameter)) {
var apiKeyHeaderValue = apiKeyHeaderValues.First();
// Get the auth token
string authToken = authorization.Parameter;
// Decode the token from BASE64
string decodedToken = Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
// Extract username and password from decoded token
userName = decodedToken.Substring(0, decodedToken.IndexOf(":"));
password = decodedToken.Substring(decodedToken.IndexOf(":") + 1);
// Verification.
if (apiKeyHeaderValue.Equals(ApiInfo.API_KEY_VALUE) && userName.Equals(ApiInfo.USERNAME_VALUE) && password.Equals(ApiInfo.PASSWORD_VALUE)) {
// Setting
var identity = new GenericIdentity(userName);
SetPrincipal(new GenericPrincipal(identity, null));
}
}
// Info.
return base.SendAsync(request, cancellationToken);
}#
endregion# region Set principal method.
/// <summary>
/// Set principal method.
/// </summary>
/// <param name="principal">Principal parameter</param>
private static void SetPrincipal(IPrincipal principal) {
// setting.
Thread.CurrentPrincipal = principal;
// Verification.
if (HttpContext.Current != null) {
// Setting.
HttpContext.Current.User = principal;
}
}#
endregion
}
}
The problem I am facing is in the SendAsync method:
AuthenticationHeaderValue authorization = request.Headers.Authorization; //This is always null even as I put [Authorize] tag above my controller
Doesn't matter if I put [Authorize], [AllowAnonymous], authorization is always null. Looking for some help. Thanks in advance!