I can't quite seem to figure out how to call HTTP POST functions from my Blazor WASM project hosted with ASP.NET. I am having trouble finding any examples of using POST methods past .NET 6 likely because it's so new. I've tried setting content-headers to JSON and many different ways of retrieving the request body from the actual controller function, but I just get 500, 415, and 400 errors. I've also tried not using model binding the the controller function, but to no avail. I do not believe this is the issue though, as using the [ApiController] attribute infers proper model binding as far as I know. I can only imagine the issue stems from the HTTP call.
The service that calls the method:
public async Task CreateUser(User user)
{
await _httpClient.PostAsJsonAsync("users", user);
}
The controller function:
[HttpPost]
public async Task PostUser(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
}
The given from the above code is just a simple 400 error.
Also, I've added a test user into the database manually, and I'm able to retrieve it without any issues.
Here's some code from one of my demo projects showing API calls to get WeatherForecast records.
Here's the Web Assembly project DataBroker:
public class WeatherForecastAPIDataBroker : IWeatherForecastDataBroker
{
private readonly HttpClient? httpClient;
public WeatherForecastAPIDataBroker(HttpClient httpClient)
=> this.httpClient = httpClient!;
public async ValueTask<bool> AddForecastAsync(WeatherForecast record)
{
var response = await this.httpClient!.PostAsJsonAsync<WeatherForecast>($"/api/weatherforecast/add", record);
var result = await response.Content.ReadFromJsonAsync<bool>();
return result;
}
public async ValueTask<bool> DeleteForecastAsync(Guid Id)
{
var response = await this.httpClient!.PostAsJsonAsync<Guid>($"/api/weatherforecast/delete", Id);
var result = await response.Content.ReadFromJsonAsync<bool>();
return result;
}
public async ValueTask<List<WeatherForecast>> GetWeatherForecastsAsync()
{
var list = await this.httpClient!.GetFromJsonAsync<List<WeatherForecast>>($"/api/weatherforecast/list");
return list!;
}
}
And here's the controller it's calling:
namespace Blazr.Demo.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private IWeatherForecastDataBroker weatherForecastDataBroker;
public WeatherForecastController(IWeatherForecastDataBroker weatherForecastDataBroker)
=> this.weatherForecastDataBroker = weatherForecastDataBroker;
[Route("/api/weatherforecast/list")]
[HttpGet]
public async Task<List<WeatherForecast>> GetForecastAsync()
=> await weatherForecastDataBroker.GetWeatherForecastsAsync();
[Route("/api/weatherforecast/add")]
[HttpPost]
public async Task<bool> AddRecordAsync([FromBody] WeatherForecast record)
=> await weatherForecastDataBroker.AddForecastAsync(record);
[Route("/api/weatherforecast/delete")]
[HttpPost]
public async Task<bool> DeleteRecordAsync([FromBody] Guid Id)
=> await weatherForecastDataBroker.DeleteForecastAsync(Id);
}
The Repo for the Demo Project Blazor.Demo
Controller Code
Data Broker Code
Related
Im pretty new to ASP core webAPI. Hopefully you guys can give me a hand.
I have been successfully using HttpGet{id} - postman has been serving the correct information back. However, I want to be able use "partNumber" in the url.
below is the working id get method:
'''
[HttpGet("{id}")]
public async Task<ActionResult<Model>> GetModel(int id)
{
return await _context.Models.FindAsync(id);
}
'''
Here is the actual parameters that I would like to search on:
```
[HttpGet("{PartNumber}")]
public async Task<ActionResult<Model>> GetModel(int partNumber)
{
return await _context.Models.FindAsync(partNumber);
}
```
Postman returns 404 not found in the latter when I should see data returned.
Any help would be greatly appreciated.
thanks
404 means the request url is not correct.
Here is my whole backend code and my controller contains [Route("api/[controller]")] attribute:
[Route("api/[controller]")]
[ApiController]
public class TestsController : ControllerBase
{
[HttpGet("{partNumber}")]
public async Task<ActionResult<Model>> GetModel(int partNumber)
{
}
}
The request url should be:https://localhost:44371/api/tests/YourPortNumber(https://localhost:44371/api/tests/1).
Result:
For NoContent return in postman,please set breakpoint and check if database contain such record with the specific partNumber:
[HttpGet("{partNumber}")]
public async Task<ActionResult<Model>> GetModel(int partNumber)
{
//set breakpoint here...
var data = await _context.Models.FindAsync(partNumber);
return await _context.Models.FindAsync(partNumber);
}
Result:
[HttpGet("{partNumber}")]
public async Task<ActionResult<Model>> GetModel(int partNumber)
{
return await _context.Models.FindAsync(partNumber);
}
The casing of the string in the route should match the parameter
I'm new to ASP.NET Core Web API and trying to implement a custom route.
Here is my controller:
using ...
namespace FoodDeliveryServer.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MenusController : ControllerBase
{
private readonly FoodDeliveryContext _context;
public MenusController(FoodDeliveryContext context)
{
_context = context;
}
// GET: api/Menus
[HttpGet]
public IEnumerable<Menu> GetMenu_1()
{
return _context.Menu;
}
// rest of the methods
// POST: api/Menus
[HttpPost]
public async Task<IActionResult> PostMenu([FromBody] Menu menu)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.Menu.Add(menu);
await _context.SaveChangesAsync();
return CreatedAtAction("GetMenu", new { id = menu.Id }, menu);
}
// POST: api/Menus/filter
[HttpPost("filter", Name = "Filtermenu")]
public async Task<IActionResult> FilterMenu([FromBody] bool isActive)
{
return Ok(_context.Menu.Where(m => m.IsActive == isActive));
}
private bool MenuExists(long id)
{
return _context.Menu.Any(e => e.Id == id);
}
}
}
Now, I'm trying to hit the filter route from POSTMAN and getting 404. Other standard routes are working fine.
POST
http://localhost:5000/api/Menus/filter
Body -> raw -> JSON
{
"isActive": true
}
Headers -> Accept -> application/json
Headers -> Content-Type -> application/json
What am I doing wrong here?
I looked at this link also but still having problems:
ASP.Net Core Web API custom route not working
You can use the [Route("")] decorator on your actions too. Otherwise it will create routes upon what you defined in StartUp.Configure.
// POST: api/Menus/filter
[Route("filter")]
[HttpPost("filter", Name = "Filtermenu")]
public async Task<IActionResult> FilterMenu([FromBody] bool isActive)
{
return Ok(_context.Menu.Where(m => m.IsActive == isActive));
}
Should work for you.
The '/api/Menus/' part will be inherited from your [Route("api/[controller]")] definition on controller level
I resolved the issue with the help of #Marius. Here is what I did:
// POST: api/Menus/filter
[HttpPost]
[Route("filter", Name = "Filtermenu")]
public async Task<IActionResult> FilterMenu([FromBody] Menu menu)
{
return Ok(_context.Menu.Where(m => m.IsActive == menu.IsActive));
}
Looks like, we need to provide a class to read FromBody
I am creating an internet store. And I want to add short URLs for products, categories and so on.
For example:
store.com/iphone-7-plus
This link should open the page with iPhone 7 plus product.
The logic is:
The server receives an URL
The server try it against existent routes
If there is no any route for this path - the server looks at a DB and try to find a product or category with such title.
Obvious solutions and why are they not applicable:
The first solution is a new route like that:
public class StringRouter : IRouter
{
private readonly IRouter _defaultRouter;
public StringRouter(IRouter defaultRouter)
{
_defaultRouter = defaultRouter;
}
public async Task RouteAsync(RouteContext context)
{
// special loggic
await _defaultRouter.RouteAsync(context);
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return _defaultRouter.GetVirtualPath(context);
}
}
The problem is I can't provide any access to my DB from StringRouter.
The second solution is:
public class MasterController : Controller
{
[Route("{path}")]
public IActionResult Map(string path)
{
// some logic
}
}
The problem is the server receive literally all callings like store.com/robots.txt
So the question is still open - could you please advise me some applicable solution?
For accessing DbContext, you could try :
using Microsoft.Extensions.DependencyInjection;
public async Task RouteAsync(RouteContext context)
{
var dbContext = context.HttpContext.RequestServices.GetRequiredService<RouterProContext>();
var products = dbContext.Product.ToList();
await _defaultRouter.RouteAsync(context);
}
You also could try Middleware to check whether the reuqest is not exist, and then return the expected response.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
app.Use(async (context,next) => {
await next.Invoke();
// add your own business logic to check this if statement
if (context.Response.StatusCode == 404)
{
var db = context.RequestServices.GetRequiredService<RouterProContext>();
var users = db.Users.ToList();
await context.Response.WriteAsync("Request From Middleware");
}
});
//your rest code
}
I am implementing CRUD operations using EF7 and storedprocudures in asp.net core web api project. I have finished implementing the get methods and left with the insert method. I am using Postman to test the web methods. I have written the implementation for Create but unable the post the information via postman isn't hitting the Create web method in the controller. Could somebody let me know what the problem could be. The route of the get and post is the same except the method signature is different.
Controller
public class MoviesController : Controller
{
private readonly IMoviesRepository _moviesRepository;
public MoviesController(IMoviesRepository moviesRepository)
{
_moviesRepository = moviesRepository;
}
[HttpGet]
[Route("api/Movies")]
public async Task<IActionResult> GetMovies()
{
var movies = await _moviesRepository.GetMovies();
var results = Mapper.Map<IEnumerable<MoviesDto>>(movies);
return Ok(results);
}
[HttpGet]
[Route("api/Movies/{ID}")]
public async Task<IActionResult> GetMovie(int ID)
{
var movie = await _moviesRepository.GetMovie(ID);
var results = Mapper.Map<IEnumerable<MoviesDto>>(movie);
return Ok(results);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route("api/Movies")]
public IActionResult CreateMovie([FromBody] MoviesDto movies)
{
if (movies == null)
{
return BadRequest();
}
// Check if movie exists
var movie = _moviesRepository.GetMovie(movies.MovieId);
if (movie == null)
{
return NotFound();
}
var results = Mapper.Map<Movies>(movies);
if (ModelState.IsValid)
{
_moviesRepository.AddMovie(results);
}
return Ok(results);
}
}
Postman
This issue has been fixed. I had to remove the anti-forgery token
I am wanting to get ahold of the Context that I am injecting into the controllers during testing and modify the data in the "in memory" version of the database context.
So the controller looks like this
[Route("api/[controller]")]
public class TestController : Controller
{
private readonly TestContext _testContext;
public TestController(TestContext testContext)
{
_testContext = testContext;
}
[HttpGet]
public IActionResult Get()
{
return Ok(new { _testContext.Users });
}
}
The test looks like this
public class SiteTests
{
[Fact]
public async Task GetIt()
{
var server = TestServer.Create(app => { app.UseMvc(); }, services =>
{
services.AddMvc();
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TestContext>(options => options.UseInMemoryDatabase());
services.AddScoped<TestContext, TestContext>();
});
var client = server.CreateClient();
var response = await client.GetAsync("http://localhost/api/test");
var content = await response.Content.ReadAsStringAsync();
Assert.True(response.IsSuccessStatusCode);
}
}
I would love to somehow get ahold of the context before the client gets the request and modify what data will be coming back from the database context.
I have the test project in GitHub
If you're targeting .NET Core, you won't be able to make use of any automatic mocking frameworks.
The best you can do is make all your methods in TestContext virtual, then extend it in your unit tests.
public class IntegrationTestContext : TestContext
{
// override methods here
}
You can then use
var context = new IntegrationTestContext();
services.AddInstance<TestContext>(context);
You can also capture any extra information you want in IntegrationTestContext and access it from within your test.