Cannot post JSON to an ASP.NET Core RazorPage handler - asp.net-core

I'm working with an ASP.NET Core RazorPage as an alternative to an MVC controller, and I want to be able to submit the client side form via XMLHttpRequest. I've already figured out the XSRF token bits so that passes the muster, but the RazorPages framework doesn't seem to process the inbound JSON payload and bind it to the property as expected.
Some code:
The page's model (.cshtml.cs):
public class IndexModel : PageModel
{
private Database database;
private ILogger logger;
[BindProperty]
public AddRequestModel MyRequest { get; set; }
public IndexModel(Database database, ILogger<IndexModel> logger)
{
this.database = database;
this.logger = logger;
}
public void OnGet() {}
public IActionResult OnPostValidate()
{
if (ModelState.IsValid)
{
// ...
}
return new BadRequestObjectResult(ModelState);
}
public async Task<IActionResult> OnPutConfirmAsync()
{
// ...
}
}
And the client side post:
const url = "?handler=validate";
const data = { MyRequest: this.fields };
await axios.post(url, data);
I have verified the data is being submitted correctly:
That X-XSRF-TOKEN header is being added by axios before the request is submitted. The fact that the server responds with a list of errors indicates that it's not the XSRF token causing the problem:
Note the MyRequest object does not contain the values from the request payload - it was not bound as expected (FirstName would not return a required error otherwise). How can I tell RazorPages to accept the JSON request and bind it to my property?

I was able to get the Binding works by adding FromBody similar to how it worked for ASP.NET Web API 2.
[BindProperty, FromBody]
public BroadcastMessageEditingViewModel BindingInfo { get; set; }

Use urlsearchparams with formdata.
In this post you can find more information How do I post form data with fetch api?

You would be better off posting your data to an API endpoint instead of a page controller. Create a class from ControllerBase and it will handle your JSON post correctly.

Related

Blazor WASM Http call is not hitting some API endpoints. Receiving index.html instead

I have a Blazor WASM page that need to make a call to get some data from an API. The Blazor app is ASPNetCore hosted, and the hosting app contains the API.
Some of my endpoints work, but some calls throw a Json serialization exception.
Unhandled exception rendering component: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
If I look at the actual response from the server, it looks like it returns the content of index.html from my WASM app.
Example Controller
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class CompanyController : ControllerBase
{
private readonly ApplicationDbContext _context;
public CompanyController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(long id)
{
Company? company = await _context.Companies.FindAsync(id);
if (company == null)
{
return NotFound();
}
return Ok(company);
}
}
Example Blazor Page
#page "/companies/{id:long}"
#attribute [Authorize]
#inject HttpClient Http
#inject NavigationManager Nav
#if (company != null)
{
<div>#company.Name</div>
}
else
{
<div>Loading Company...</div>
}
#code {
private Company? company;
[Parameter]
public long Id { get; set; }
protected override async Task OnInitializedAsync()
{
try
{
company = await Http.GetFromJsonAsync<Company>($"/api/company/{Id}");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}
In the example above, everything works as expected. But if I make the following two changes, I'll get the Json Exception mentioned above.
Create an identical controller named WorkOrderController. Everything else is identical including pulling the Company data from the database. Only the name of the controller is different.
Change the Http request to company = await Http.GetFromJsonAsync<Company>($"/api/workOrder/{Id}"); in the Blazor page.
Why would some endpoints work, and some wouldn't?
So, the requestUri passed to GetFromJsonAsync must be lowercase. My request was failing because I had a capital "O" in "workOrder".
I am not sure why this is a requirement of the request parameter, but alas, making the path lowercase fixed the issue.

ASP.NET Core Web API FromHeader binding x-api-key header

I have a x-api-key header that I want to bind to my controller paramter. I tried the below code but the parameter is still null.
[HttpGet]
public ActionResult Get([FromHeader] xApiKey) {
var apikey = xApiKey;
}
After 5 minutes of posting this question I found the answer here. So using the name property of FromHeader.
[HttpGet]
public ActionResult Get([FromHeader(Name = "x-api-key")] apiKey) {
// code here
}

Decompressing requests using ServiceStack within a .Net Core Alpine container

I'm building a containerized micro-service that uses ServiceStack running with .Net Core on the ASPNET Core Alpine docker image. I want to be able receive compressed requests containing Gzipped JSON within the request body, have the request decompressed before it hits ServiceStack so that the request DTO is populated based on the decompressed JSON data.
So far I have tried rolling my own middle-ware to do this, but my request DTO is still populated with Nulls. I have also tried using Anemonis.AspNetCore.RequestDecompression middleware, with the same result. I'm now wondering if the middle-ware is not being called before ServiceStack receives the request, or even not being called at all.
Using the Anemonis middle-ware, my Startup.cs initializes the middle-ware as such:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseRequestDecompression();
app.UseServiceStack(new AppHost
{
AppSettings = new NetCoreAppSettings(Configuration),
});
//...
}
With decompression provided within ConfigureServices:
public new void ConfigureServices(IServiceCollection services)
{
//...
services.AddRequestDecompression(o =>
{
o.Providers.Add<DeflateDecompressionProvider>();
o.Providers.Add<GzipDecompressionProvider>();
o.Providers.Add<BrotliDecompressionProvider>();
});
//...
}
And for further detail, ServiceStack service model and interface as usual:
[Route("/sample/full", "POST")]
public class SampleFull : IReturn<SampleResponse>
{
public long code { get; set; }
public SampleData[] data { get; set; }
}
public class SampleData
{
public string field1 { get; set; }
public string field2 { get; set; }
}
public class SampleService : Service
{
public object Post(SampleFull request)
{
try
{
// Do some processing
return new SampleResponse()
{
// Response details
}
}
catch (Exception ex)
{
return new SampleResponse()
{
// Error details
};
}
}
}
Using Postman to test, Content-Encoding = gzip, with a gzipped file as the request body, when Post(SampleFull request) is called, request is populated with null values for both code and data.
Has anyone been able to get this working? I'm now thinking that I could be missing a library/package within the ASPNET Core Alpine container.
Answering my own question as I have finally found the issue. As it turns out, by default, Postman will send a Content-Type header with a value of application/octet-stream when you select to send a binary file as the request body. This header is hidden by default, and you can't overwrite it, but you can disable it and add your own Content-Type header with a value of application/json, which then allows ServiceStack to correctly populate it's request DTO. Solved.

Asp.Net Core 2.1 - Authorize based on content in request

I am exposing an endpoint for integration with a 3rd party and their requirement is for me to authorize their requests to my endpoint based on a key passed in the body being posted. My code will then needs to validate that the passed key matches some predetermined value on my side. The incoming model will look something like this:
public class RequestBase
{
public string ApiKey { get; set; }
...
}
Exploring the options for Authorization in ASP.NET Core I don't really see a match for what I am attempting to do. I am thinking a custom AuthorizeAttribute from this question would work but I'm not having any luck and get a 401 regardless of what I do. This is what I have so far:
[AttributeUsage(AttributeTargets.Class)]
public class MyAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private static IEnumerable<string> _apiKeys = new List<string>
{
"some key... eventually will be dynamic"
};
public void OnAuthorization(AuthorizationFilterContext context)
{
var req = context.HttpContext.Request;
req.EnableRewind();
using (var reader = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
{
var bodyStr = reader.ReadToEnd();
var isAuthorized = _apiKeys.Any(apiKey => bodyStr.Contains(apiKey));
if (!isAuthorized)
{
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
return;
}
}
req.Body.Position = 0;
}
}
When the key is not found in the body the 403 is returned as expected. However, when the key is found the result I get back is still a 401. Almost seems as if the base.OnAuthorization is being called. I have other endpoints that use a standard AurhorizeAttribute. They work as expected when only if I pass in a JWT.
Questions:
Am I on the right path with a custom AuthorizeAttribute or is there a better way?
If a customer AuthorizeAttribute is the right path... what am I missing?
Appreciate any help!
For using your own authorize logic with IAuthorizationFilter, you should not use with AuthorizeAttribute which will check the Authentication with default authentication schema.
Try to change AuthorizeAttribute to Attribute.
[AttributeUsage(AttributeTargets.Class)]
public class KeyAuthorizeAttribute : Attribute, IAuthorizationFilter
{

Web API 2 Post 404s, but Get works

I'm confused... I have a very simple Web API and controller, which works fine if I have a GET request, but 404's if I have a POST request.
[RoutePrefix("api/telemetry/trial")]
public class LoginTelemetryController : ApiController
{
[Route("login")]
[HttpPost]
public IHttpActionResult RecordLogin(string appKey) {
using (var context = new Core.Data.CoreContext()) {
context.ActivityLogItems.Add(new Domain.Logging.ActivityLogItem()
{
ActivityType = "Trial.Login",
DateUtc = DateTime.UtcNow,
Key = new Guid(appKey)
});
context.SaveChanges();
}
return Ok();
}
When I post against this in postman, I get:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost:47275/api/telemetry/trial/login'.",
"messageDetail": "No action was found on the controller 'LoginTelemetry' that matches the request."
}
If I change it to a [HttpGet] and put the appKey as a querystring, all is fine.
My app startup is very simple:
public void Configuration(IAppBuilder app)
{
log4net.Config.XmlConfigurator.Configure();
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes(); // <------ HERE
FilterConfig.RegisterHttpFilters(httpConfig.Filters);
LoggingConfig.RegisterHandlers(httpConfig.Services);
ConfigureOAuth(app);
ConfigureWebApi(httpConfig);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(httpConfig);
}
Can anyone spot why POST requests aren't being found? Thanks
If I take string parameter out and replace it with a request object, it works...
Instead of: public IHttpActionResult RecordLogin(string appKey)
I create a request model class:
public class PostLoginTelemetryRequest{
public string appKey {get;set;}
}
Then alter the signature:
public IHttpActionResult RecordLogin(PostLoginTelemetryRequest request)
Everything works fine (why it can't take a regular string like MVC5 web dev, I don't know, but anyway...)
(also note that I had tried this in every format from the client with the string method: form-url-encode, raw body, etc, so I'm fairly certain it wasn't a calling format issue).