I'm building an ASP.Net Web API application and i have the following code...
public IHttpActionResult GetCustomers() {
var customers = context.Customers.ToList();
return Ok(customers);
}
I'm using the Ok() method to return customers because i'm using an IHttpActionResult return type.
Now if i have the following method
public void DeleteCustomer(int id) {
var customerInDb = context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null) {
NotFound();
}
context.Customers.Remove(customerInDb);
context.SaveChanges();
}
Can I use NotFound() method here when the return type of my ActionMethod is void???
Void does not have a return type. So you can try to call NotFound(), but I'm not sure if this would even compile - Haven't tried. Why don't you just go with an out of the box IHttpActionResult?
public IHttpActionResult DeleteCustomer(int id)
{
var customerInDb = context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null)
{
return NotFound();
}
context.Customers.Remove(customerInDb);
context.SaveChanges();
return Ok(customerInDb);
}
Using IHttpActionResult is the more elegant version. If the id is invalid, you can just safely exit your method and tell the calling client that something went wrong. If everything went well, you're just giving the client a thumbs-up. IF you return your deleted entity or just an empty Ok() should not matter at this point.
Using void may or may not delete the entity in your data storage. The client would never know, because the server would not return any response.
Related
like this:
[HttpGet]
[AllowAnonymous]
[Route("[action]")]
public ActionResult Get([FromQuery] object RequestModel)
{
try
{
IResult runResult = new Result();
ICommand api = Factory.APIName((Enums.APIName)Enum.ToObject(typeof(Enums.APIName), 9901));
var responseModel = api.HandleCommand<object, object>(RequestModel, out runResult);
api.Dispose();
if (runResult.Success)
return StatusCode(200, responseModel);
else
return StatusCode(200, new ERC_Models.BaseResponse() { StateCode = 999 });
}
catch
{
return StatusCode(500);
}
}
how can do it?
maybe use with Generic? but I tried, it response 404
the reason is I wish all entrance can through one webapi, and go factory layer
so I need can take any parameters webapi action...
thanks!
I am using Entity Framework API and I am trying to update just one column using the Put method...
[ResponseType(typeof(void))]
[Authorize]
public IHttpActionResult PutLCTimeSlots(int id, LCTimeSlots lCTimeSlots)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != lCTimeSlots.id)
{
return BadRequest();
}
db.Entry(lCTimeSlots).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!LCTimeSlotsExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
My question, what do I need to change in this method to only update one column?
I have tried replacing db.Entry(lCTimeSlots).State = EntityState.Modified; with db.Entry(lCTimeSlots).Property(x => x.taken).IsModified = true; But that didn't work....anyone have any ideas?
You shouldn't use the default PUT for such an operation as it implies a client should be able to update more than a single property. I'd suggest a PUT operation using a route that describes the property being updated w/ parameter of the property's type instead of an LCTimeSlots object:
[HttpPut( "{id}/yourProperty" )]
[Authorize]
public IHttpActionResult YourProperty( int id, TypeOfProperty yourProperty )
{
// validate value of `yourProperty` if you can before loading entity from DB
// load entity from DB
var loadedEntity = dbContext.Set<LCTimeSlots>().Find( id );
// if not found, 404 it
// update property, e.g.:
loadedEntity.YourProperty = yourProperty;
// validate entity in its entirety if necessary
// save changes
}
I'll start by suggesting use of the PATCH verb if you only want to modify certain properties.
Also, it's not a great idea to accept an entity object from the client, instead accept a model object that only has the properties that you aim to modify with this method.
Lastly, verify that the entity exists before attempting to make any change.
Now, do something like this:
var timeSlot = db.LCTimeSlots.SingleOrDefault(e => e.Id == model.Id);
if (timeSlot != null)
{
db.Entry(timeSlot).CurrentValues.SetValues(model);
db.SaveChanges();
}
else
{
//404
}
I'm just getting to grips with creating a new WebAPI2 project in ASP.NET. I'm trying to get the controller to return data from a DTO I have created rather than the raw object classes that EF created. I've been following a tutorial on Microsoft Docs and have got my method which returns all records to work using the DTO, but I can't figure out how to correctly modify the method which only returns the record with the ID matching the passed parameter using an asynchronous task, like the default method does.
The default method generated by Visual Studio looks like this:
[ResponseType(typeof(Post))]
public async Task<IHttpActionResult> GetPost(int id)
{
Post post = await db.Post.FindAsync(id);
if (post == null)
{
return NotFound();
}
return Ok(post);
}
and I've got my modified method looking like this:
[ResponseType(typeof(PostDTO))]
public async Task<IHttpActionResult> GetPost(int id)
{
var _post = from p in db.Post
where p.PostID == id
select new PostDTO()
{
PostID = p.PostID,
SubmitTime = p.SubmitTime,
SubmitUsername = p.SubmitUsername,
};
if (_post == null)
{
return NotFound();
}
return Ok(_post);
}
This methods works just fine, but as you can see, it doesn't make use of .NET's Await/Async feature to perform the query asynchronously. I'll be honest and admit that I don't actually know if this matters, but I feel like if the default method was asynchronous, so should mine be. I just can't work out where to insert the Async and Await keywords to make this work.
You can use this method as,
[ResponseType(typeof(PostDTO))]
public async Task<IHttpActionResult> GetPost(int id)
{
var _post = await (from p in db.Post
where p.PostID == id
select new PostDTO()
{
PostID = p.PostID,
SubmitTime = p.SubmitTime,
SubmitUsername = p.SubmitUsername,
}).ToListAsync();
if (_post == null)
{
return NotFound();
}
return Ok(_post);
}
From JavaScript client code I am creating the following data:
var employee = {
FirstName: "Rudolf",
Salary: 99
};
I then pass this through an Ajax call to an MVC Web API Controller Action:
using System.Web.Http.OData;
public async Task<IHttpActionResult> Patch([FromUri] int employeeId, [FromBody] Delta<Employee> employee)
{
await _employeeService.Patch(employeeId, employee);
return Ok();
}
This calls my service to update the database as follows:
public async Task Patch(int employeeId, Delta<Employee> employee)
{
using (var context = new DBEntities())
{
if (employee.TryGetPropertyValue("Salary", out object salary))
{
var ss = Convert.ToDouble(salary); // Always 0
}
if (employee.TryGetPropertyValue("FirstName", out object firstName))
{
var ss = Convert.ToString(firstName); // Expected value
}
var currentEmployee = await context.Employees
.FirstOrDefaultAsync(e => e.Id == employeeId);
if (currentEmployee == null)
return;
employee.Patch(currentEmployee);
await context.SaveChangesAsync();
}
}
Note: I missed out some of the details for brevity as the actual client-server call is working fine.
The code seems to work as expected, but the Salary property (the only none-string one) is always set to 0 (zero). So that field never get's updated.
Any ideas why the Salary is not being passed through?
Note: I use very similar client-server code for GET/POST/PUT/DELETE and they all work fine, so I believe it is related to the Delta<> part.
Yes, I encountered the same problem with int properties.
I solved the problem using SimplePatch (v1.0 is only 10KB).
Disclaimer: I'm the author of the project.
It is inspired to Microsoft.AspNet.WebApi.OData but SimplePatch has no dependencies.
How to use
Install the package using:
Install-Package SimplePatch
Your MVC Web API Controller Action becomes:
using SimplePatch;
public async Task<IHttpActionResult> Patch([FromUri] int employeeId, [FromBody] Delta<Employee> employee)
{
await _employeeService.Patch(employeeId, employee);
return Ok();
}
Then your Patch method becomes:
public async Task Patch(int employeeId, Delta<Employee> employee)
{
using (var context = new DBEntities())
{
if (employee.TryGetPropertyValue("Salary", out object salary))
{
var ss = Convert.ToDouble(salary);
}
if (employee.TryGetPropertyValue("FirstName", out object firstName))
{
var ss = Convert.ToString(firstName);
}
var currentEmployee = await context.Employees
.FirstOrDefaultAsync(e => e.Id == employeeId);
if (currentEmployee == null)
return;
employee.Patch(currentEmployee);
await context.SaveChangesAsync();
}
}
Also, SimplePatch gives you the ability to ignore some properties when calling the Patch method.
Global.asax or Startup.cs
DeltaConfig.Init((cfg) =>
{
cfg.ExcludeProperties<Employee>(x => x.YourPropertyName);
});
If Salary has type int then there is an issue with that type
The Json parser converts integers to 64 bit ones, which won't match the 32 bit int properties declared on the entity and thus get ignored by this method.
This problem is mentioned here
So, you can use long instead of int or using the OData media type formatters
See this
Given the below OData custom action. Is it possible to get a more refactor friendly binding of the action parameters?
Both magic strings has to be exactly the same: .Parameter<int>("Rating") and (int)parameters["Rating"]. Which is bound to break at some point in the future.
Config
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>()
.Action("Rate")
.Parameter<int>("Rating");
Controller
[HttpPost]
public async Task<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
int rating = (int)parameters["Rating"];
db.Ratings.Add(new ProductRating
{
ProductID = key,
Rating = rating
});
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException e)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
Request
POST http://localhost/Products(1)/ProductService.Rate HTTP/1.1
Content-Type: application/json
Content-Length: 12
{"Rating":5}
I tried putting the parameter directly in the method. But I couldn't get it to work.
public async Task<IHttpActionResult> Rate([FromODataUri] int key, int rate)
According to How to pass an objet as a parameter to an OData Action using ASP.NET Web Api? it seems it's possible to use an Object but I only have a primitive type as the parameter.
Please advice :)
I think you are talking about this issue https://github.com/OData/WebApi/issues/777
The ODataActionParameters make thing complicate when there is only one action parameter, we will try to have a workaround or design for this after breaking change 6.0 release.