I'm moving some of internal projects from NET Core 2.0 to 3.0, and having trouble with getting the controller to execute after middleware has finished. Honestly I'm a bit frustrated as a similar approach used to work with NET Core 2.0.
I've uploaded my test project to GitHub:
https://github.com/wonea/MVC-API-Routing-Test
The test project details three middleware stages; SecurityMiddleware, UserValidatorMiddleware, WebSocketMiddleware. So upon booting the API you can set breakpoints on each of the individual stages and they will be hit in the correct order. However upon passing the HTTPContext on the final middleware stage it returns but does not hit the controller.
In Startup.cs I configure my services:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddApplicationInsightsTelemetry(_configuration);
// memory cache
services.AddMemoryCache();
services.AddMvc()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.IgnoreNullValues = true;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
// CORS
var corsBuilder = new CorsPolicyBuilder();
services.AddCors(builder =>
{
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin();
corsBuilder.WithOrigins("*");
corsBuilder.AllowCredentials();
});
}
I've detailed the request pipeline, setting up WebSockets, performing the middleware, and then triggeringmy controller as an endpoint.
public void Configure(IApplicationBuilder app, IHostApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);
Console.CancelKeyPress += (sender, eventArgs) =>
{
appLifetime.StopApplication();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
app.UseRouting();
app.UseSecurityMiddleware();
app.UseUserValidation();
// websockets
var webSocketOptions = new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(120)
};
app.UseWebSockets(webSocketOptions);
app.UseWebSocketMiddleware();
// put last so header configs like CORS or Cookies etc can fire
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
I'm only ever going to need one controller, so don't need any fancy config.
I see your controller is setup like
[Route("api/[controller]")]
public class MainController : Controller
{
[HttpGet]
public string Get()
Note the docs say
Replace [controller] with the name of the controller, which by convention is the controller class name minus the "Controller" suffix.
For example, I have a "Work" controller that looks like
[Route("api/Work")]
[ApiController]
public class WorkController : ControllerBase
{
(it also has the ApiController attribute).
This would then be accessed at httpx://localhost/api/Work
You can then specify other endpoints on the api with the parameter to the HttpGet or HttpPost attribute.
[HttpGet("Test")]
public async Task<ActionResult<string>> Test()
{
Would be httpx://localhost/api/Work/Test
Figured it out, my routing was at fault.
My launch settings were point directly to the controllers location
Then my controller was duplicating the routing. So the main controller was being resolved on.
localhost:51234/api/main/api/main
I've flattened the main controller's routing now
Related
I'm currently upgrading a project from ASP.NET WebAPI 5.2.6 (OWIN) to ASP.NET Core 2.1.1 (Kestrel).
Our project is a single page application and we communicate via WebAPI with the client. Therefore I wanted to annotate the controllers wit the new ApiController attribute.
Unfortunately it seems that the binding source parameter inference isn't working as expected (at least for me). I assumed based on the docs, that complex types (e.g. my LoginRequest) are inferred as [FromBody].
Code (Controller & Startup)
// AccountController.cs
[Route("/account"), ApiController]
public class AccountController : ControllerBase
{
[HttpPost("backendLogin"), AllowAnonymous]
public async Task<ActionResult<LoginResponse>> BackendLogin(LoginRequest lr)
{
await Task.CompletedTask.ConfigureAwait(false); // do some business logic
return Ok(new LoginResponse {UserId = "123"});
}
// Models
public class LoginRequest {
public string Email { get; set; }
public string Password { get; set; }
}
public class LoginResponse {
public string UserId { get; set; }
}
}
// Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvcCore()
.AddJsonFormatters(settings => {
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<ApiBehaviorOptions>(options => {
// options.SuppressConsumesConstraintForFormFileParameters = true;
// options.SuppressInferBindingSourcesForParameters = true;
// options.SuppressModelStateInvalidFilter = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
Problem
Calling the controller from the client via Ajax call (Content-Type: application/x-www-form-urlencoded;) results in a 400 (bad request) response, with content {"":["The input was not valid."]}. On the server I get the following trace output:
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor: Information: Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.SerializableError'.
If I change the options.SuppressInferBindingSourcesForParameters in ConfigureServices to true, it seems to work. This is strange, since this setting should disable the binding inference or have I misconceived something? Is this a bug in ASP.NET core or am I missing something?
Btw. it also works if I ommit the ApiController attribute, but I guess this is not the real solution to this problem.
Furthermore I would be happy if I don't need to change anything on the client side (adding headers, change content types, ...), because there are a lot of Ajax calls out there and I just want to upgrade the server side components.
I also asked this question on the official ASP.NET Core MVC repo.
One of the members (pranavkm) came back with an answer, which I will just quote here:
ApiController is designed for REST-client specific scenarios and isn't designed towards browser based (form-urlencoded) requests. FromBody assumes JSON \ XML request bodies and it'll attempt to serialize it, which is not what you want with form url encoded content. Using a vanilla (non-ApiController) would be the way to go here.
So for now I will omit the [ApiController] attribute.
In a later step I may change the client calls to use a JSON body, so I can readd the attribute.
Environment:
Visual Studio 2017 Community with latest updates
Target Framework: .NET Core 2.1 (latest version)
SignalR Core
Running on IIS Express on Windows 10 (dev environment)
TL;DR: Injecting IHubContext<> into Controller ctor so Action method can send message to clients doesn't seem to be working.
Long version:
I have a basic ASP.NET Core test application working and .NET clients are able to connect and send/receive messages. So my Hub and Clients appear to be working fine.
I'm now trying to add a controller to the same VS project that the SignalrR Hub is in so that external actors can send messages via a REST API endpoint.
To do this I've tried using DI to inject IHubContext<> into the ctor of my controller as follows:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : Controller
{
private IHubContext<OrgHub> _hubContext;
public ValuesController(IHubContext<OrgHub> hubContext)
{
_hubContext = hubContext;
}
//...
}
This appears to be successfully injecting the right IHubContext because when I debug the private members I see the number of connections = 1 when I have 1 .NET client connected.
Now the trouble: within an action method I try to use the _hubContext to call a hub method... but nothing happens. The debugger passes by the line of code and no breakpoints within my Hub are hit. Nothing happens at all. Note that when .NET clients send messages (via the SignalR .NET client) the breakpoints on my Hub are indeed hit. Its just the _hubContext in my Controller/action method that doesn't seem to be working.
Here is what I'm doing in an action method:
// GET api/values
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> GetAsync()
{
//Try to call "SendMessage" on the hub:
await _hubContext.Clients.All.SendAsync("SendMessage", "SomeUserName", "SomeMessage");
//...
return new string[] { "bla", "bla" };
}
And here is the corresponding Hub method:
public class OrgHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
//...
}
If it helps, here is the edited version of Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSignalR(routes =>
{
routes.MapHub<OrgHub>("/rpc");
});
app.UseMvc();
}
}
So, any ideas or suggestions on where to go from here? Clearly there must be something I'm overlooking...
thanks!
That's not how this works. When you call SendAsync that message is going out to the client. You don't call methods on your hub, via SendAsync. Nothing is happening, because the clients are literally getting sent a message that should invoke something listening for "SendMessage" client-side, which presumably is not something you've register your clients to listen for. If the goal is to hit "ReceiveMessage" client-side, then you should be doing SendAsync("ReceiveMessage", ...) in your controller.
I'm trying to get Policy based Authorization working in .net core 2.1 based on this article: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1
However I cannot seem to get it to fire.
In the below example, I've commented out the context.suceeed line, so I would have thought my api call to my usercontroller would fail.
However my API call is being accepted.
What am I doing wrong?
This is my startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorizationHandler, VerifyAuthCookieHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("VerifyAuthCookie", policy =>
policy.Requirements.Add(new VerifyAuthCookieRequirement()));
});
services.AddMvcCore().AddJsonFormatters();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
}
Here is my handler
public class VerifyAuthCookieHandler : AuthorizationHandler<VerifyAuthCookieRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
VerifyAuthCookieRequirement requirement)
{
//context.Succeed(requirement);
return Task.CompletedTask;
}
}
And here is my requirement:
public class VerifyAuthCookieRequirement : IAuthorizationRequirement
{
public VerifyAuthCookieRequirement()
{
}
}
And finally, my controller:
[Route("api/[controller]")]
[Authorize(Policy = "VerifyAuthCookie")]
public class UserController : Controller
{
}
If I add code in HandleRequirementAsync and set a breakpoint then it's not being hit when I debug, so my Handler doesn't appear to be called at all.
You should call app.UseAuthentication(); before the app.UseMvc(); in the Configure method of the Startup class. This will add the ASP.NET Core authentication middleware to the request pipeline.
Since you are using services.AddMvcCore() we'll need to configure the authorization services manually, something services.AddMvc() does for you automatically. We should add .AddAuthorization() to the IMvcCoreBuilder.
This will add the default Authentication- and Authorization services and a PolicyEvaluator.
If you're interested in the exact services that will be registered in your DI container you should follow this link.
I had similar issue, I fix it by :
services.AddMvcCore().AddAuthorization().AddJsonFormatters();
Following the docs here I tried to implement a policy-based auth scheme. http://docs.asp.net/en/latest/security/authorization/policies.html#security-authorization-handler-example
I ran into the issue that my Handle method was not being called on my custom AuthorizationHandler. (It does not throw here). It also does inject the dependency currently in the constructor.
Here it the AuthorizationHandler Code.
using WebAPIApplication.Services;
using Microsoft.AspNet.Authorization;
namespace WebAPIApplication.Auth
{
public class TokenAuthHandler : AuthorizationHandler<TokenRequirement>, IAuthorizationRequirement
{
private IAuthService _authService;
public TokenAuthHandler(IAuthService authService)
{
_authService = authService;
}
protected override void Handle(AuthorizationContext context, TokenRequirement requirement)
{
throw new Exception("Handle Reached");
}
}
public class TokenRequirement : IAuthorizationRequirement
{
public TokenRequirement()
{
}
}
}
In Start Up I have
// Authorization
services.AddSingleton<IAuthorizationHandler, TokenAuthHandler>()
.AddAuthorization(options =>
{
options.AddPolicy("ValidToken",
policy => policy.Requirements.Add(new TokenRequirement()));
});
The controller method is
// GET: api/values
[HttpGet, Authorize(Policy="ValidToken")]
public string Get()
{
return "test";
}
Hitting this endpoint returns nothing and there is a warning in the console of
warn: Microsoft.AspNet.Mvc.Controllers.ControllerActionInvoker[0]
Authorization failed for the request at filter 'Microsoft.AspNet.Mvc.Filters.AuthorizeFilter'.
I am able to hit other endpoints that don't have the attribute successfully.
SOS,
Jack
I'm putting this here for reference because I spent way too long figuring this out...
I had implemented a custom requirement and handler (empty for testing's sake):
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
public class TestHandler : AuthorizationHandler<TestRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class TestRequirement : IAuthorizationRequirement
{
}
Registered it in my Startup.cs ConfigureServices() section:
services.AddAuthorization(options =>
{
options.AddPolicy("Test", policy => policy.Requirements.Add(new TestRequirement()));
// Other policies here
}
Added it to my controller method:
[HttpGet]
[Authorize(Policy = "Test")]
public IActionResult Index()
{
Return View();
}
But was getting a 403 error (not 401) with every request to the controller method!
Turns out, I was not registering TestHandler with the ConfigureServices() (Dependency Injection) section of Startup.cs.
services.AddSingleton<IAuthorizationHandler, TestHandler>();
Hope this saves someone from banging their head on their desk. :|
The answer to this question is alluded to in a comment to adem caglin, so props to him.
The issue is that the AuthorizeFilter is rejecting the request before the AuthorizationHandler is being called. This is because for every use of the Authorize tag MVC adds AuthorizeFilter ahead of the AuthorizationHandler in the pipeline. This AuthorizeFilter checks to see if any of the current users identities are authorized. In my case there were no authorized identities associated with any user so this would always fail.
A solution (which IMO is somewhat hackish) is to insert a peice of middleware that will get executed before any MVC code. This middleware will add a generic authenticated identity to a User (if the user does not already have one).
Consequently the AuthorizeFilter check will pass and the Handle method on the AuthenticationHandler method will be executed and our problem will be solved. The middleware code (which needs to be added to Configure before app.UseMvc(); is called) is as follows
app.Use(async (context, next) =>
{
if (!context.User.Identities.Any(i => i.IsAuthenticated))
{
context.User = new ClaimsPrincipal(new GenericIdentity("Unknown"));
}
await next.Invoke();
});
An alternative way to override the AuthorizeFilter is outline here (Override global authorize filter in ASP.NET Core MVC 1.0)
Citing the response from here (Asp.Net Core policy based authorization ends with 401 Unauthorized)
Take a look at Asp.net Core Authorize Redirection Not Happening i think adding options.AutomaticChallenge = true; solves your problem.
For this current project I am working on, we need to implement a web api. It needs to live inside the existing webforms project. And the specifications say we need to use Owin.
So after wiring everything up using: Microsoft.Owin, Microsoft.Owin.Host.SystemWeb, Microsoft.Owin.Hosting, Microsoft.Owin.Security
A proper startup class with the OwinStartupAttribute.
app.UseWebApi with a windsor IOC container.
Web api seems to work as expected.
Except for the fact that all requests made to the existing website also go through to webapi.
A bit more explanation.
We needed a LanguageMessageHandler : DelegatingHandler. After setting that class up we've started noticing that the breakpoint on 'SendAsync gets caught even when we are not requesting anything webApi related.
The older website shouldn't even have knowledge about this handler.
A bit code the clarify:
The startupclass:
[assembly: OwinStartupAttribute(typeof(Startup))]
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = ((IContainerAccessor)HttpContext.Current.ApplicationInstance).Container;
app.UseWebApi(container);
}
}
The UseWebApi extension:
public static void UseWebApi(this IAppBuilder app, IWindsorContainer container)
{
var config = new HttpConfiguration
{
DependencyResolver = new WindsorDependencyResolver(container)
};
//Web API Routes
config.MapHttpAttributeRoutes();
//Default to json when requested by browser
config.Formatters.JsonFormatter.MediaTypeMappings.Add(new RequestHeaderMapping("Accept", "text/html", StringComparison.InvariantCultureIgnoreCase, true, "application/json"));
//Add language handler
config.MessageHandlers.Add(new LanguageMessageHandler());
//Ensure initialized
config.EnsureInitialized();
//Start WebApi
app.UseWebApi(config);
}
So now we are trying to figure out why all the requests are handled by the LanguageMessageHandler and not just the requests that are made for webApi.
An example route:
[RoutePrefix("api/dossier")]
public class AdministrationsController : ApiController
{
//GET
[Route("{idtype}_{id}/administrations/planned/")] //?limit={maxdate}&nursingunit={nuid}
[HttpGet]
public IHttpActionResult Planned(string idtype, int id, [FromUri] int maxdate = 6, [FromUri] int? nuid = null)
{
return Ok();
}
}
Fixed by using a filter instead of a message handler.
Was wrongfully asuming that message handler was going to be executed after routing in the pipeline.