Asp.net Core 3.1 controller methods with parameters from body is not working - asp.net-core

I have WebAPI that working under IIS reverce proxy. WepAPI working as windows service.
Controller:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[Route("version-post-body")]
public IActionResult VersionPost([FromBody] Test test)
{
return Ok(test.TestString);
}
public class Test
{
[JsonPropertyName("test")]
public string TestString { get; set; }
}
IIS Rewrite
<rule name="MyRewrite">
<match url="api/v2/(.*)" />
<action type="Rewrite" url="http://localhost:10126/{R:1}" />
</rule>
When I send request, i get 408 HTTP Code. But if controller method does't expect parameters from body, it's working fine.
/// WOrk
[HttpPost]
[Route("post-body-without-body")]
public IActionResult Post()
{
return Ok("test");
}
/// not work
[HttpPost]
[Route("post-body")]
public IActionResult Post([FromBody] List<string> test)
{
return Ok(string.Join(";", test));
}
What could be the problem?

Related

Blazor and WebApi in same solution: Why is the POST response 405: Method not allowed?

I've got two Project in one solution:
WebApi
Blazor wasm
I start both projects with
In Program.cs is an external HttpClient added
builder.Services.AddHttpClient("WEbApi", client => client.BaseAddress = new Uri("http://localhost:12639"));
The Sender from my Index.razor.cs
using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Components;
public partial class Index
{
[Inject] private IHttpClientFactory ClientFactory { get; set; }
public void Send()
{
var client = ClientFactory.CreateClient("WEbApi");
var res = client.PostAsJsonAsync("order","FOO");
}
}
In the WEbApi Project the received controller code:
using Microsoft.AspNetCore.Mvc;
[Route("[controller]")]
public class OrderController : Controller
{
[HttpGet]
public IActionResult Index()
{
return Ok("GET done");
}
[HttpPost]
public IActionResult ExecuteOrder([FromBody] string order)
{
return Ok("POST done");
}
}
The GET request return OK.
The Problem:
client.PutAsJsonAsync("order","FOO") response a 405: method not allowed (listened via wireshark).
I try a POST with Postman, and it works!
I have to configure CORS in tzh WebApi project. Now it works.
Insert this code in the public void Configure() in the Startup.cs
app.UseCors(cors => cors
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true)
.AllowCredentials()
);

How do you handle failure to authenticate user in a custom AuthenticationHandler?

I have a scenario where an app needs to authenticate a user by calling an API and sending a user token to verify user identity. I started working on a custom authentication handler based on the following tutorials:
Tutorial 1
Tutorial 2
I have a very basic example which right now simply fails the authentication just to make sure it works:
public class SoleAuthenticationHandler : AuthenticationHandler<SoleAuthenticationOptions>
{
private readonly ISoleApiService _soleApiService;
public SoleAuthenticationHandler(
IOptionsMonitor<SoleAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock, ISoleApiService soleApiService)
: base(options, logger, encoder, clock)
{
_soleApiService = soleApiService;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("You are not authorized to access this resource."));
}
}
This works as intended, controller actions decorated with [Authorize] attribute are intercepted and 401 is thrown. My questions are the following:
How am I supposed to handle the 401 once it happens? For example let's say I want to redirect a user to a friendly page that says "you're not authorized please login". Is that something done in the handler or elsewhere? What is the proper process here? Looking at Microsoft docs for the AuthenticationHandler there is a method called BuildRedirectUri but providing that method with a uri does not really change anything - the page still returns a 401.
As it stands now in order for this to work I need to decorate controllers/actions with [Authorize] attribute. Is there a way to do this globally so that I don't have to specifically authorize each controller and/or action?
We had/have the customErrors pages in ASP.NET web forms and MVC 5.x to redirect users automatically to the specified error pages when a certain statusCode is issued:
<customErrors mode="On" defaultRedirect="error">
<error statusCode="404" redirect="error/notfound" />
<error statusCode="403" redirect="error/forbidden" />
</customErrors>
Here in ASP.NET Core we can simulate these pages this way:
First add a new ErrorController to handle specific statusCodes (id's here) and then return custom views for different conditions:
public class ErrorController : Controller
{
private readonly ILogger<ErrorController> _logger;
public ErrorController(ILogger<ErrorController> logger)
{
_logger = logger;
}
public IActionResult Index(int? id)
{
var logBuilder = new StringBuilder();
var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
logBuilder.AppendLine($"Error {id} for {Request.Method} {statusCodeReExecuteFeature?.OriginalPath ?? Request.Path.Value}{Request.QueryString.Value}\n");
var exceptionHandlerFeature = this.HttpContext.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandlerFeature?.Error != null)
{
var exception = exceptionHandlerFeature.Error;
logBuilder.AppendLine($"<h1>Exception: {exception.Message}</h1>{exception.StackTrace}");
}
foreach (var header in Request.Headers)
{
var headerValues = string.Join(",", value: header.Value);
logBuilder.AppendLine($"{header.Key}: {headerValues}");
}
_logger.LogError(logBuilder.ToString());
if (id == null)
{
return View("Error");
}
switch (id.Value)
{
case 401:
case 403:
return View("AccessDenied");
case 404:
return View("NotFound");
default:
return View("Error");
}
}
}
Now it's time to connect this controller to the built-in error handling middlewares of ASP.NET Core:
public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
{
app.UseDatabaseErrorPage();
app.UseDeveloperExceptionPage();
}
app.UseExceptionHandler("/error/index/500");
app.UseStatusCodePagesWithReExecute("/error/index/{0}");
About your second question, just define your filter/Authorize attribute globally.

Endpoints not being discovered in PageModel when Published to IIS10: Http Response 404 .Net Core RazorPages

When debugging in IIS Express all endpoints are reachable via GET. When published to IIS10 I can navigate to the page public void OnGet() is being called and renders the razor page. When calling ./MyPage/Partial on server IIS10 I receive a 404 Not Found error and this does not happen on IIS Express in Visual Studio.
public class IndexModel : PageModel
{
[BindProperty]
public MyModel MyModel { get; set; }
[HttpGet]
public void OnGet()
{
...
}
[HttpGet]
public IActionResult OnGetPartial([FromQuery] int id)
{
...
}
}
I have followed the instructions on https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2 and my best guess is that I need to configure these routes as per https://learn.microsoft.com/en-us/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-2.2
Although my question is why in IIS Express I can call javascript jquery $.load('./MyPage/Partial?id=1') and it works fine and when published it returns a 404 error? And what would be the specific solution?
EDIT: in my Index.cshtml I have the following #page "{handler?}" at the top in order to handle the custom REST methods.
In order to solve this I followed the instructions from https://learn.microsoft.com/en-us/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-2.2 in the file Startup.cs or whichever class you are using in Program.cs via
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseStartup<Startup>();
In the method in the file Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddRazorPages(options => options.Conventions.AddPageRoute("/MyPage", "/MyPage/Partial/{id}")).AddRazorViewEngine().AddViews();
// Other service code impl. here
}

How can I redirect users, who or not logged in to login page, in C# MVC 4.5 if they try to access other site pages via URL

I have one website, where I want users to be redirected to "Login" page if they are not signed in. The users may try to access the webpages by posting url. I want to do it in C# MVC 4.5
Here I dont want the action "[Authorize]" to be available unless signed in.
It is index action to view index page.
//Login Controller
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(FormCollection frm)
{
....//other code
}
[Authorize]
public ActionResult Index()
{
List<DTO> obj = objConfigurator.GetDataList();
return View(obj);
}
public ActionResult Edit(int Id)
{
DTO obj = objC.GetListById(Id);
return View(obj);
}
Use the [Authorize] attribute on your controller.
[Authorize]
public class YourController: Controller
{
. . .
[AllowAnonymous]
public ActionResult Register()
{
}
[AllowAnonymous]
public ActionResult LogIn()
{
}
. . .
}
Also, you have to add your login page in the web.config -
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Login" timeout="2880" />
</authentication>
</system.web>
You have another, even better option, to register AuthorizeAttribute as global filter in the global.asax file.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
....
filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}
This way, you only have to apply the [AllowAnonymous] to actions tha you want to be visited by anonimous users.

401 code not handled in mvc project properly

I have ASP.NET MVC4 project with custom AuthorizeAttribute to control the authorization. In order to explain my situation easier I created a dummy project to illustrate the issue.
I have one single controller
using System.Web.Mvc;
using MvcApplication2.Helper;
using MvcApplication2.Models;
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new ViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(ViewModel model)
{
Session["ModelValue"] = model.InputValue;
return RedirectToAction("Step2");
}
[MyAuthorize]
public ActionResult Step2()
{
return View();
}
}
}
The purpose is very simple, From Index I accept some input, store the value in a session and redirect to Step2. I have my authorize attribute on step 2. The code for my attribute is
using System;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication2.Helper
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["ModelValue"] == null)
{
return false;
}
else
{
string inputValue = httpContext.Session["ModelValue"] as string;
if (inputValue != "1")
{
return false;
}
else
{
return true;
}
}
}
}
}
The purpose is very simple, check if the session value is 1 or not.
Run the application, you input 1 in textbox and you see step2 view, if you input anything else you get the default 401.0 page.
Now I opened the web.config file to include
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="401" redirect="~/401.htm" />
</customErrors>
<compilation debug="true" targetFramework="4.0" />
</system.web>
I hope when the application captures 401 code, I should see the content of 401.htm. But the reality is that I still get the default 401 error display from server.
Any idea why?
In addition use this:
<system.webServer>
<httpErrors>
<error statusCode="401" path="~/Home/Login"/>
<error statusCode="404" path="~/Error/NotFound"/>
</httpErrors>
</system.webServer>