I am disgusted not have found a solution to this problem.
I started creating a new api using Web API 2 and just cannot get the POST and PUT to work. The Get all and Get single item works perfectly fine.
There are no related articles anywhere, and those that i've found relates only to Gets and Web API, but not Web API 2.
Any assistance would do please.
// POST: api/checkOuts
[HttpPost]
[ResponseType(typeof(checkOut))]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IHttpActionResult> PostcheckOut(checkOut co)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.checkOuts.Add(checkOut);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (checkOutExists(checkOut.id))
{
return Conflict();
}
else
{
throw;
}
}
return CreatedAtRoute("DefaultApi", new { id = checkOut.id }, checkOut);
}
So basically, I'm just attempting to get a debug into the method.
Was especially disappointed in this link as it covered almost everything, but ai. http://www.asp.net/web-api/overview/web-api-routing-and-actions/create-a-rest-api-with-attribute-routing
Regards
This is a working code
// POST api/values
[HttpPost]
[ResponseType(typeof(CheckOut))]
public async Task<IHttpActionResult> Post([FromBody] CheckOut checkOut)
{
if (checkOut == null)
{
return BadRequest("Invalid passed data");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.checkOuts.Add(checkOut);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (checkOutExists(checkOut.id))
{
return Conflict();
}
else
{
throw;
}
}
return CreatedAtRoute("DefaultApi", new { id = checkOut.Id }, checkOut);
}
I've declared CheckOut class to be like this :
public class CheckOut
{
public int Id { get; set; }
public string Property2 { get; set; }
}
The Key things here are :
1- You need to add [FromBody] to your Api method.
2- I've tested it using Fiddler,
i- by choosing POST action.
ii- content-type: application/json.
iii- passing {"Id":1,"Property2":"Anything"} in the message body.
Hope that helps.
Related
I can't figure out how to edit the row after seeing the changes in DB.
I have an API-project and an MVC-project. I use CRUD in my API and call them with my MVC with HttpClient
I have a public byte[] RowVersion { get; set; } property with the attribute [Timestamp].
I have a clientFactory where I do CreateClient() to perform PutAsync("api.example.com/{id}") action.
The HttpResponseMessage variable on my putasync action returns StatusCode(409) because my API successfully detected a concurrency conflict.
I managed to display error messages before updating the concurrency; showing the newly updated rows in the database(newsDb) with help of a new client, clientFactory.CreateClient(), and comparing them with the inputs(news).
Then I set the news.RowVersion = newsDb.RowVersion and re-display View(news).
And after clicking Save again, nothing happens - no redirects, no changes - the concurrency errors are still there:
[HttpPost("edit/{id}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditNewsArticle(int id, [Bind("NewsId,Author,Title,Content,CreatedDate,HashTags,RowVersion")] News news)
{
if (id != news.NewsId)
{
return NotFound();
}
if (ModelState.IsValid)
{
news.UpdatedDate = DateTime.Now;
string json = JsonConvert.SerializeObject(news);
HttpResponseMessage putTask = await clientFactory.CreateClient().PutAsync($"https://localhost:44331/api/News/{id}", new StringContent(json, Encoding.UTF8, "application/json"));
if (putTask.IsSuccessStatusCode)
{
return RedirectToAction(nameof(Index));
}
else if (putTask.StatusCode == HttpStatusCode.Conflict)
{
string jsonDb = await clientFactory.CreateClient().GetStringAsync($"https://localhost:44331/api/News/{id}");
News newsDb = JsonConvert.DeserializeObject<News>(jsonDb);
if (newsDb is null)
{
ModelState.AddModelError(string.Empty, $"Unfortunately, the news item you edited has already been deleted by another user.");
}
if (newsDb.Title != news.Title)
{
ModelState.AddModelError("Title", $"Title in database: {newsDb.Title}");
}
if (newsDb.Author != news.Author)
{
ModelState.AddModelError("Author", $"Author in database: {newsDb.Author}");
}
if (newsDb.Content != news.Content)
{
ModelState.AddModelError("Content", $"Content in database: {newsDb.Content}");
}
if (newsDb.HashTags != news.HashTags)
{
ModelState.AddModelError("HashTags", $"HashTags in database: {newsDb.HashTags}");
}
ModelState.AddModelError(string.Empty,
"Editing was canceled as the selected news item was changed by someone else in the meantime." +
"The values of the change are now shown below, which are derived from the database" +
"If you still want to edit the user, click Save again.");
news.RowVersion = newsDb.RowVersion;
}
else
{
ModelState.AddModelError(string.Empty, "Unknown error. Contact a support.");
return View(news);
}
}
return View(news);
}
API Put:
[HttpPut("{id}")]
public async Task<IActionResult> PutNews(int id, [FromBody] News news)
{
if (id != news.NewsId)
{
return BadRequest();
}
context.Entry(news).State = EntityState.Modified;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!NewsExists(id))
{
return NotFound();
}
else
{
return StatusCode(409);
}
}
return CreatedAtAction("GetNews", new { id = news.NewsId }, news);
}
I found my issue. I needed to call ModelState.Clear(); after de-serializing the 'jsonDb', and also remove RowVersion from Bind in the attribute.
Actually I'm working using dapper.
LoginAuditModel:
public class LoginAuditModel
{
public IList<GuidIdTableType> UserIdTableType { get; set; } = new List<GuidIdTableType>();
public DateTime StartingDate { get; set; }
public DateTime EndingDate { get; set; }
}
Repository:
public async Task<IEnumerable<LoginAuditGetViewModel>> LoginAuditGet(LoginAuditModel model)
{
try
{
async Task<IEnumerable<LoginAuditGetViewModel>> DoLoginAuditGet()
{
using (var connection = _connectionManager.GetOpenConnection(_configuration.GetConnectionString(connectionstring)))
{
return await connection.QueryAsync<LoginAuditGetViewModel>("[dbo].[spName]", param: new
{
UserIdTableType = ((List<GuidIdTableType>)model.UserIdTableType).ToDataTable(),
model.StartingDate,
model.EndingDate
}
, commandType: CommandType.StoredProcedure);
}
}
return await DoLoginAuditGet();
}
catch (Exception ex)
{
throw ex;
}
}
Service:
public async Task<IEnumerable<LoginAuditGetViewModel>> LoginAuditGet(LoginAuditModel model)
{
async Task<IEnumerable<LoginAuditGetViewModel>> DoLoginAuditGet()
{
return await _employeeRepository.LoginAuditGet(model);
}
return await DoLoginAuditGet();
}
Controller:
[HttpGet]
public async Task<IActionResult> LoginAuditGet([FromQuery]LoginAuditModel model)
{
try
{
async Task<IActionResult> DoLoginAuditGet()
{
var rModel = await _employeeService.LoginAuditGet(model);
if (rModel is null || !rModel.Any()) return NotFound();
return Ok(rModel);
}
return await DoLoginAuditGet();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
When I execute this code using swagger in my case, table valued parameter is always passing with count = 0 (UserIdTableType), but for some reason, when I change controller method to [HttpPost]
it pass parameter correctly! and everything it's working fine:
[HttpPost]
public async Task<IActionResult> LoginAuditGet(LoginAuditModel model)
{
try
{
async Task<IActionResult> DoLoginAuditGet()
{
var rModel = await _employeeService.LoginAuditGet(model);
if (rModel is null || !rModel.Any()) return NotFound();
return Ok(rModel);
}
return await DoLoginAuditGet();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
So, my question is why it is working as a Post method and not with Get? I need to change something to works with Get? Regards
In your case, you want to send an array of objects as a query string in URL, I think this is impossible, but you can send an array of base data types such as int, string ... etc.
but in Post and Put, it is sending the data as body and have another type of data transfer.
and you should know, there is a limitation on Query String length, you can have a look here: click me
I'm not able to access to my delete method of my api rest.
If i write the method like this it work:
[Route("api/Document/{documentId:int}")]
[HttpDelete]
public IHttpActionResult Delete([FromUri]int documentId,[FromBody] int [] documentsId)
{
try
{
documentCtrl = documentCtrl ?? new DocumentCtrl();
return Ok(documentCtrl.Delete(documentsId));
}
catch (DocumentNotFoundException)
{
return NotFound();
}
catch (Exception)
{
return InternalServerError();
}
}
It works, but if i put:
[Route("api/Document/MassiveDelete")]
[HttpDelete]
public IHttpActionResult MassiveDelete([FromBody] int[] ids)
{
try
{
documentCtrl = documentCtrl ?? new DocumentCtrl();
return Ok(documentCtrl.MassiveDelete(ids));
}
catch (DocumentNotFoundException)
{
return NotFound();
}
catch (Exception)
{
return InternalServerError();
}
}
I don't have acces, any ideas what could it be?
This is my request code:
DeleteDocument(id: number): Observable<boolean> {
return this._httpService.delete(AppModule.service + 'Document/' + id, AppModule.options)
.map((response: Response) => <boolean>response.json())
.catch(this.handleError);
}//This work if i want to delete one
DeleteDocuments2(ids:Array<number>):Observable<boolean>{
AppModule.options.body=ids;
return this._httpService.delete(AppModule.service + 'Document/MassiveDelete', AppModule.options)
.map((response: Response) => <boolean>response.json())
.catch(this.handleError);
}
You cannot send two parameters in your Api, you need to createa custom class like follow and send as follows,
MyCustomRequest {
public int[] documentIds;
public int documentId;
}
and then,
public IHttpActionResult MassiveDelete([FromBody] MyCustomRequest request)
you can access it as,
request.documentIds;
request.documentId;
I created a simple Web API with ASP.NET Core. I have the following API:
GET /api/messages - get all messages
GET /api/messages/{id} - get a message by id
POST /api/messages - add a new message
PUT /api/messages/{id} - update an existing message
DELETE /api/messages/{id} - delete a message
Now, I want another API to get all messages by message owner's name.
What I tried:
I tried to create this API, but it doesn't work because it conflicts with GET /api/messages/{id}:
GET /api/messages/{name} <- (doesn't work due to conflicting API)
// GET: api/messages/{name}
[HttpGet("{name}")]
public IEnumerable<Message> GetMessagesByName(string name)
{
return _repository.GetMessages().Where(m => m.Owner == name);
}
Here is my Message model Message.cs:
public class Message
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string Owner { get; set; }
public string Text { get; set; }
}
Here is my Messages controller MessagesController.cs:
[Route("api/[controller]")]
public class MessagesController : Controller
{
private readonly IMessageRepository _repository;
public MessagesController(IMessageRepository repository)
{
_repository = repository;
}
// GET: api/messages
[HttpGet]
public IEnumerable<Message> Get()
{
return _repository.GetMessages();
}
// GET api/messages/{id}
[HttpGet("{id}", Name = "GetMessage")]
public IActionResult GetById(long id)
{
var message = _repository.GetMessage(id);
if (message == null)
{
return NotFound();
}
return new ObjectResult(message);
}
// POST api/messages
[HttpPost]
public IActionResult Post([FromBody]Message message)
{
if (message == null)
{
return BadRequest();
}
_repository.AddMessage(message);
return CreatedAtRoute("GetMessage", new { id = message.Id }, message);
}
// PUT api/messages/{id}
[HttpPut("{id}")]
public IActionResult Put(long id, [FromBody]Message message)
{
if (message == null || message.Id != id)
{
return BadRequest();
}
var messageToUpdate = _repository.GetMessage(id);
if (messageToUpdate == null)
{
return NotFound();
}
messageToUpdate.Owner = message.Owner;
messageToUpdate.Text = message.Text;
_repository.UpdateMessage(messageToUpdate);
return new NoContentResult();
}
// DELETE api/messages/{id}
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var message = _repository.GetMessage(id);
if (message == null)
{
return NotFound();
}
_repository.RemoveMessage(id);
return new NoContentResult();
}
}
Question:
How can I create an API method to get all messages by message owner's name?
Ideally, I would like the API to look like GET /api/messages/{name}, but don't think its possible since it conflicts with GET /api/messages/{id}.
I'm thinking of creating the API like this, but I'm not sure how.
GET /api/messages/name/{name} <- (or something along that line)
Solution:
To have GET /api/messages/{name} working without conflicting with GET /api/messages/{id}, change attribute [HttpGet("{id}", Name="GetMessage")] to [HttpGet("{id:long}", Name="GetMessage")] for public IActionResult GetById(long id) method.
To also have GET /api/messages/name/{name} working, add [Route("name/{name}")] attribute to public IEnumerable<Message> GetMessagesByName(string name) method.
you can put parameter type in route, so your code method should be look like that:
// GET api/messages/{id}
[HttpGet("{id:long}", Name = "GetMessage")]
public IActionResult GetById(long id)
{
var message = _repository.GetMessage(id);
if (message == null)
{
return NotFound();
}
return new ObjectResult(message);
}
I think, web api is ignoring parameters types in routes if they are not typed explicitly, so in your example it has two routes like this: api/messages/{object} and when you put explicit type, they are like this: api/messages/{object} and api/messages/{long}
I was wondering if it was possible to return a bad request with content from an MVC Controller? The only way I have been able to do this is to throw HttpException however here I can't set any content. Tried this approach to but for some odd reason I am always getting an OK back. Is it possible to do this?
public class SomeController : Controller
{
[HttpPost]
public async Task<HttpResponseMessage> Foo()
{
var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.Content = new StringContent("Naughty");
return response;
}
}
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "naughty");
Set the Http status code to bad request and use Content method to send your content along with response.
public class SomeController : Controller
{
[HttpPost]
public async Task<ActionResult> Foo()
{
Response.StatusCode = 400;
return Content("Naughty");
}
}
In addition to the #Ekk's answer, make sure to check this:
ASP.NET+Azure 400 Bad Request doesn't return JSON data
Add the following entry to your 'web.config'.
<system.webServer>
<httpErrors existingResponse="PassThrough"/>
</system.webServer>
...
Of course you can.
Take a look at my Action
// GET: Student/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
I think this is best practice
to return HttpStatusCodeResult(HttpStatusCode.BadRequest); in case user does not provided a required value
to return HttpNotFound(); in case the user provided a required value but not veiled
hope this help you
You can pass in error message to the second parameter like so:
return new HttpResponseMessage(HttpStatusCode.BadRequest, "Your message here");
The TrySkipIisCustomErrors flag can be used to turn off IIS custom error handling.
[HttpGet]
public void Foo()
{
HttpContext.Response.TrySkipIisCustomErrors = true;
HttpContext.Response.StatusCode = 400;
HttpContext.Response.Write("Naughty");
}
Answer for .Net Core: Return IActionResult as documented here. For example:
public IActionResult Get(int id = 0)
{
try
{
var obj = _myRepo.Get(id);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
return Ok(obj);
}