How Many Delete or Put methods in a WebApi Controller? - c#

I have a problem with ASP.NET WebAPI`. I wanted to keep in RESTful API, so I create some apis in one controller, such as:
GET /api/user/ - userslist
GET /api/user/{id} - a user
GET /api/user/{id}/books - get all books of the user
Delete /api/user/{id} - delete the user
Delete /api/user/{id}/books - delete users' books
All the Get methods works fine, but only one Delete method can work, when the other was called, it will got a 405 response. Could you help me ?
Thanks. here is my controller
[RoutePrefix("api/Car")]//AttributeRouting asp.net 4.0 (not 5.0)
public class CarController : ApiController
{
public CarInfoPagedList GetCarInfos(int page = 0, int limit = 10)
{
var list = new CarInfoPagedList();
return list;
}
[HttpRoute("{id}")]
public string GetCarById(string id, string time = " ")
{
return "some";
}
[HttpRoute("{id}/runtime")]
public CarState GetCarRuntime(string id) {
return new CarState(id);
}
[DELETE("{id}")]//405
public void DeleteCar(string id)
{
//TODO:remove car
}
[DELETE("{id}/terminal/{terminalId}")]//work fine
public void Delete(string id, string terminalId)
{
}
[PUT("{id}/")]//405
public void PutCar(string id, CarInfo car)
{
}
[PUT("{id}/terminal/{terminalId}")]//work fine
public void Put(string id, string terminalID)
{
}
}

Related

HttpResponseMessage post API json format with C#

I used the HttpResponseMessage Post method to let the mobile terminal verify the account password. I used the following CODE to run successfully, but the POST format must be run like this.
'{"ID":"xxx","Password":"xxx"}'
It need two ' can run, I don't know why.
I can't request a service using the normal POST format on iOS or Android.
The format I want is {"ID":"xxx","Password":"xxx"},without '
[HttpPost]
public HttpResponseMessage Post([FromBody] string DATA)
{
using (appapidataEntities entities = new appapidataEntities())
{
//string controllerName = ControllerContext.RouteData.Values["controller"].ToString();
JObject jo = JObject.Parse(DATA);
string id = jo["ID"].ToString();
string password = jo["Password"].ToString();
var user = entities.USERs.Where(x => x.ID == id && x.Password == password).FirstOrDefault();
var result = new
{
message = "failure"
};
var result2 = new
{
message = "success"
};
if (user == null)
{
return Request.CreateResponse(HttpStatusCode.OK, result);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, result2);
}
}
}
public partial class USER
{
public string ID { get; set; }
public string Password { get; set; }
}
}
Please have someone with experience to help me, thank you very much.
As #Nkosi said, the correct way to receive a complex object is using a class/object (also called 'model binding')
In your models add this class. This class will be the 'contract' between the service with any external application who calls the service. Usually, any client (service, app or frontend) also has a class with this contract to call the service.
public class LoginViewModel {
public string ID { get; set; }
public string Password { get; set; }
}
Now, modify the controller as follow
[HttpPost]
public HttpResponseMessage Post([FromBody] LoginViewModel DATA) {
using (appapidataEntities entities = new appapidataEntities())
string id = DATA.ID;
string password = DATA.Password;
// rest of the code
}
}
Make sure the device is sending the data the service is waiting (maybe adding a breakpoint if you are debugging from Android Studio before to make the request) and add a breakpoint in your controller to verify that the variable DATA has the correct values.

Angularjs is sending null values to API, why?

I am sending an id and a list from angular to C# web API, but it is received by null when debugging the method, without knowing the reason.
angular service
this.deletevacs = function (staffid, vacs) {
return $http.post("/AssignUserToDepartmentapi/api/assignuser/deletevacs?staffid=" + staffid + '&vacs=' + JSON.stringify(vacs))
}
angular js
var result = DevExpress.ui.dialog.confirm("Are you sure want to delete vacations assigned employees ?", "Confirm changes");
result.done(function (dialogResult) {
if (dialogResult) {
var promisePost = Assignments.deletevacs($scope.SelectedEmp1.staffkey, $scope.oldvacs);
promisePost.then(function (pl) {
toastr.success("Successfully deleted");
C#
[HttpPost]
public HttpResponseMessage deletevacs(int staffid,int[] vacs)
{
try
{
obj.ExecNonQuery(string.Format("delete from HR_AssRole_Dep where staff_key={0} and Role=7 and Current_Flag=1 and VacMKey ={1}"
, staffid
, vacs));
}
catch (Exception ex)
{
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
any help, thanks in advance
new C#
[HttpPost]
public HttpResponseMessage deletevacs([FromBody] role r)
{
obj.ExecNonQuery(string.Format("delete from HR_AssRole_Dep where staff_key={0} and Role=7 and Current_Flag=1 and VacMKey ={1}"
,r.Staff_Key
, r.VacMKey));
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
class
public class role
{
//public int Ass_Dep_Key { get; set; }
public int Dep_Key { get; set; }
public int Staff_Key { get; set; }
public int Role { get; set; }
public List<int> VacMKey { get; set; }
public short SelfApprov { get; set; }
}
new angular
var deletevacssss = { Staff_Key: $scope.SelectedEmp1.staffkey, VacMKey: $scope.selectedvacs };
var result = DevExpress.ui.dialog.confirm("Are you sure want to delete vacations assigned employees ?", "Confirm changes");
result.done(function (dialogResult) {
if (dialogResult) {
var promisePost = Assignments.deletevacs(deletevacssss);
promisePost.then(function (pl) {
toastr.success("Successfully deleted");
new angular service
this.deletevacs = function (deletes) {
return $http.post("/AssignUserToDepartmentapi/api/assignuser/deletevacs", deletes)
}
when i make a debug, the r object from role class get the staffkey from angular correctly but the list of vacmkey count by 0 ???
you should pass the parameter values for post method in params property .
var data = {staffid: staffid, vacs:vacs};
$http({
url: "/AssignUserToDepartmentapi/api/assignuser/deletevacs",
method: "POST",
params: data
})
or try this below way
var data = {staffid: staffid, vacs:vacs};
$http.post("/AssignUserToDepartmentapi/api/assignuser/deletevacs", data ).then(function (data, status, headers, config) {
alert("success");
},function (data, status, headers, config) {
alert("error");
});
API code should be like
[HttpPost]
public HttpResponseMessage deletevacs([FromUri]int staffid, [FromUri] int[] vacs)
{
...
}
Solution 1:
Use [FromUri] attribute in your HttpPost method parameter since you are getting the value from the querystring in the Url.
Try this:
[HttpPost]
public HttpResponseMessage deletevacs([FromUri]int staffid, [FromUri] int[] vacs)
{
try
{
obj.ExecNonQuery(string.Format("delete from HR_AssRole_Dep where staff_key={0} and Role=7 and Current_Flag=1 and VacMKey ={1}"
, staffid
, vacs));
}
catch (Exception ex)
{
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Solution 2:
Create an object that contains the staffId and vacs properties and assign it to the parameter. Add the [FromBody] attribute to before the object parameter. Then in your httpPost call, put the parameter inside the body of the request instead of the Url.
Google about the use of [FromBody] in webapi.
Solution 3:
Istead of using HttpPost, why not use HttpDelete method. It is clear in your problem that your intention is to delete records. In web API, you can use the HttpDelete attribute instead of HttpPost. To do this, change your attribute to HttpDelete from HttpPost, and in your paramter, you can just pass the [FromUri] int StaffId only and inside your API, all the vacs record related that StaffId, you can delete programmatically in DB. When calling your API use the $http.delete endpoint method instead.

Returning multiple values from WebApi (post) to AngularJs

I am using .net core C#, WebApi & AngularJs.
For saving data my Angularjs code makes a $http call to my WebApi. I can return single data from my api fine but not sure whats the best method to return multiple values here. I can make it comma separated and then return as well, but wanted to know if there is a better approach to this.
So basically when the API saves data to my db, I want to return a variable, boolean value if the save was successful and an exception message in case the save was not successfully. Below is my code.
AngularJs Code:
service.saveData(data).then(function (res) {
//get someDataToReturn, dataSaved & exception raised if any from db save here.
}, function (err) {
});
WebApi Code:
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
bool dataSaved = true;
string someDataToReturn = string.Empty;
//do some processing and updating someDataToReturn here
//Saving data to DB
dataSaved = SaveData(data);
//I want to return someDataToReturn, dataSaved(true or false) and exception raised from SaveData if any
return Ok(someDataToReturn);
}
//DB Call to save data
public bool SaveData(List<UserData> data)
{
try
{
foreach (var set in data)
{
//creating query etc
_db.Execute(query);
}
return true;
}
catch (SqlException ex)
{
}
return false;
}
Let me know the best approach for this.
First of, you should check if the values in your request body is correctly populated.
Take a look at DataAnnotations.
You can use annotations to specify which properties in your model that are Required, Min and Maxlength etc.
Here's an example on how to define a Name property to be required on the UserData class
public class UserData
{
[Required]
public string Name { get; set; }
}
If the request model do not fulfill the specified rules set on the UserData class DataAnnotations, the context ModelState will be set to false and contain the DataAnnotations errors.
This can be used to determind if the current request is a bad request and return a proper http status code from that.
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); //will return a 400 code
...
Then regarding the SaveData method. Capture the exception in the controller and return a proper status code from there
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
if (!ModelState.IsValid)
return BadRequest(ModelState); //400 status code
try
{
SaveData(data);
}
catch(Exception e)
{
return InternalServerError(e); //500 status code
}
string someDataToReturn = string.Empty;
return Ok(someDataToReturn ); //200 status code
}
public void SaveData(List<UserData> data)
{
foreach (var set in data)
{
//creating query etc
_db.Execute(query);
}
}
You can use the Controller class method Json(object data). Something like:
[HttpPost("data/save")]
public async Task<IActionResult> SaveData([FromBody] List<UserData> data)
{
return this.Json(SaveData(data));
}
See this.
you can create an entity and return it
public class BaseResult{
public bool Result{get;set;}
public string Errors{get;set;}
}
or only
return Ok( new { result = dataSaved , error= exception.Message});
the standard way is:
return 201 status code
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201
[HttpPost("data/save")]
public async Task<IHttpActionResult> SaveData([FromBody] List<UserData> data)
{
try
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
// return response of 201 if you created the resource successfully
// typically return this with a uri to the new resource
return Created("location", saveData(data));
}
catch (Exception)
{
return InternalServerError();
}
}

Can I use Content Negotiation to return a View to browers and JSON to API calls in ASP.NET Core?

I've got a pretty basic controller method that returns a list of Customers. I want it to return the List View when a user browses to it, and return JSON to requests that have application/json in the Accept header.
Is that possible in ASP.NET Core MVC 1.0?
I've tried this:
[HttpGet("")]
public async Task<IActionResult> List(int page = 1, int count = 20)
{
var customers = await _customerService.GetCustomers(page, count);
return Ok(customers.Select(c => new { c.Id, c.Name }));
}
But that returns JSON by default, even if it's not in the Accept list. If I hit "/customers" in my browser, I get the JSON output, not my view.
I thought I might need to write an OutputFormatter that handled text/html, but I can't figure out how I can call the View() method from an OutputFormatter, since those methods are on Controller, and I'd need to know the name of the View I wanted to render.
Is there a method or property I can call to check if MVC will be able to find an OutputFormatter to render? Something like the following:
[HttpGet("")]
public async Task<IActionResult> List(int page = 1, int count = 20)
{
var customers = await _customerService.GetCustomers(page, count);
if(Response.WillUseContentNegotiation)
{
return Ok(customers.Select(c => new { c.Id, c.Name }));
}
else
{
return View(customers.Select(c => new { c.Id, c.Name }));
}
}
I think this is a reasonable use case as it would simplify creating APIs that return both HTML and JSON/XML/etc from a single controller. This would allow for progressive enhancement, as well as several other benefits, though it might not work well in cases where the API and Mvc behavior needs to be drastically different.
I have done this with a custom filter, with some caveats below:
public class ViewIfAcceptHtmlAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.HttpContext.Request.Headers["Accept"].ToString().Contains("text/html"))
{
var originalResult = context.Result as ObjectResult;
var controller = context.Controller as Controller;
if(originalResult != null && controller != null)
{
var model = originalResult.Value;
var newResult = controller.View(model);
newResult.StatusCode = originalResult.StatusCode;
context.Result = newResult;
}
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
which can be added to a controller or action:
[ViewIfAcceptHtml]
[Route("/foo/")]
public IActionResult Get(){
return Ok(new Foo());
}
or registered globally in Startup.cs
services.AddMvc(x=>
{
x.Filters.Add(new ViewIfAcceptHtmlAttribute());
});
This works for my use case and accomplishes the goal of supporting text/html and application/json from the same controller. I suspect isn't the "best" approach as it side-steps the custom formatters. Ideally (in my mind), this code would just be another Formatter like Xml and Json, but that outputs Html using the View rendering engine. That interface is a little more involved, though, and this was the simplest thing that works for now.
I haven't tried this, but could you just test for that content type in the request and return accordingly:
var result = customers.Select(c => new { c.Id, c.Name });
if (Request.Headers["Accept"].Contains("application/json"))
return Json(result);
else
return View(result);
I liked Daniel's idea and felt inspired, so here's a convention based approach as well. Because often the ViewModel needs to include a little bit more 'stuff' than just the raw data returned from the API, and it also might need to check different stuff before it does its work, this will allow for that and help in following a ViewModel for every View principal. Using this convention, you can write two controller methods <Action> and <Action>View both of which will map to the same route. The constraint applied will choose <Action>View if "text/html" is in the Accept header.
public class ContentNegotiationConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
if (action.ActionName.ToLower().EndsWith("view"))
{
//Make it match to the action of the same name without 'view', exa: IndexView => Index
action.ActionName = action.ActionName.Substring(0, action.ActionName.Length - 4);
foreach (var selector in action.Selectors)
//Add a constraint which will choose this action over the API action when the content type is apprpriate
selector.ActionConstraints.Add(new TextHtmlContentTypeActionConstraint());
}
}
}
public class TextHtmlContentTypeActionConstraint : ContentTypeActionConstraint
{
public TextHtmlContentTypeActionConstraint() : base("text/html") { }
}
public class ContentTypeActionConstraint : IActionConstraint, IActionConstraintMetadata
{
string _contentType;
public ContentTypeActionConstraint(string contentType)
{
_contentType = contentType;
}
public int Order => -10;
public bool Accept(ActionConstraintContext context) =>
context.RouteContext.HttpContext.Request.Headers["Accept"].ToString().Contains(_contentType);
}
which is added in startup here:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o => { o.Conventions.Add(new ContentNegotiationConvention()); });
}
In you controller, you can write method pairs like:
public class HomeController : Controller
{
public ObjectResult Index()
{
//General checks
return Ok(new IndexDataModel() { Property = "Data" });
}
public ViewResult IndexView()
{
//View specific checks
return View(new IndexViewModel(Index()));
}
}
Where I've created ViewModel classes meant to take the output of API actions, another pattern which connects the API to the View output and reinforces the intent that these two represent the same action:
public class IndexViewModel : ViewModelBase
{
public string ViewOnlyProperty { get; set; }
public string ExposedDataModelProperty { get; set; }
public IndexViewModel(IndexDataModel model) : base(model)
{
ExposedDataModelProperty = model?.Property;
ViewOnlyProperty = ExposedDataModelProperty + " for a View";
}
public IndexViewModel(ObjectResult apiResult) : this(apiResult.Value as IndexDataModel) { }
}
public class ViewModelBase
{
protected ApiModelBase _model;
public ViewModelBase(ApiModelBase model)
{
_model = model;
}
}
public class ApiModelBase { }
public class IndexDataModel : ApiModelBase
{
public string Property { get; internal set; }
}

WebService(WebApi) and Console C# to access it, getting error 500

I'm trying to create a web api to send data to a database and receive other info. My web api will be in the center of 2 different application that i doesn't own!
But when I try to reach my WebApi from a console application, I'm always getting the
"(500) Internal Server Error."
So here is my code and my class (At the moment I'm practicing in a fake project to get experience)
BTW I use .Net 4 (not 4.5)
Everything is in a Big project and separated with solution that I will say with each class
Solution : DataModel
Class : Permission
public class Permission
{
public int PermissionId { get; set; }
[MaxLength(50)]
public string Type { get; set; }
}
Solution : WebService
Class : PermissionController : ApiController
public class PermissionController : ApiController
{
[HttpGet]
public object Get([FromUri] int id)
{
Permission permission = PermissionDal.GetPermissionById(id);
if (permission == null)
{
return new { Success = Dal.Error };
}
return new { Success = Dal.Done, Permission = permission };
}
}
Solution : DataAccess
Class : PermissionDal
public static class PermissionDal
{
public static Permission GetPermissionById(int id)
{
return (from p in db.DbPermission where p.PermissionId == id select p).FirstOrDefault();
}
}
Solution : Console
Class : Program
static void Main(string[] args)
{
using (var client = new WebClient())
{
client.Headers.Clear();
client.Headers.Add("content-type", "application/json");
string result = client.DownloadString("http://localhost:50171/api/Permission?id=1");
Console.WriteLine(result);
}
}
My webservice solution is really running on port 50171 from the visual studio properties of the project WebService.
I tried
string result = client.DownloadString("http://localhost:50171/api/Permission/1");
I tried
string result = client.DownloadString("http://localhost:50171/api/permission?id=1");
I tried
string result = client.DownloadString("http://localhost:50171/api/permission/1");
I'm always getting the same error.

Categories