MVC Web API Post method with custom action name - c#

And I wrote custom action names for ever process.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In webapi config I wrote like this and in controller, I started like below
[System.Web.Http.HttpPost]
public HttpResponseMessage Control(List<string>codes,string updateperson,string refPeriod, int actualyear, int actualmonth)
{
For get methods, everything works well but for post method it doesn't work and gives error like below.
In body in post I send
{codes: ["CPM2018-004"], updateperson: "E3852", refPeriod: "SFC18", actualyear: 2018, actualmonth: 12}
Request URL:http://localhost:50941/Api/Investment/Control Request
Method:POST Status Code:404 Not Found Remote Address:[::1]:50941
Referrer Policy:no-referrer-when-downgrade
How can I sreceive post requests to web API with custom action name?

Create model to hold value being posted.
public class ControlViewModel {
public List<string> codes { get; set; }
public string updateperson { get; set; }
public string refPeriod { get; set; }
public int actualyear { get; set; }
public int actualmonth { get; set; }
}
And then update the action to expect the data in the BODY of the request
public class InvestmentController : ApiController {
[HttpPost]
public HttpResponseMessage Control([FromBody]ControlViewModel data) {
//...
}
//...
}
Reference Parameter Binding in ASP.NET Web API

Related

Product code causing web mvc api call to fail

I am using mvc5 web API to power a mobile app in xamrian, for the most part, the web API calls work 100% however today I came across a situation that where there was a full stop in the product code caused the call to the web API to fail.
But today I had a product code like this TFNT1116.5 and the API call failed.
public async Task<StockWarehouseInfoBins> GetWarehouseBinsInformaiton(string ItemCode, string WarehouseName)
{
List<StockWarehouseInfoBins> _result = new List<StockWarehouseInfoBins>();
var uri = new Uri(string.Format(BaseUrl + Constants.BinDetails + ItemCode.Trim(), string.Empty));
var response = await _client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var byteArray = await response.Content.ReadAsByteArrayAsync();
var content = Encoding.UTF8.GetString(byteArray, 0, byteArray.Length);
_result = JsonConvert.DeserializeObject<List<StockWarehouseInfoBins>>(content);
}
return _result.FirstOrDefault();
}
My Client is a singleton of an HTTP client class.
HttpClient _client;
I think the problem is to do with the full stop in the product code.
This is the call that it gets to in my MVC controller.
[HttpGet]
// GET: Warhouses/Details/5
public ActionResult BinDetails(string id)
{
List<StockWarehouseInfoBins> result = new List<StockWarehouseInfoBins>();
result = database.GetWarehouseDetails(id);
return Json(result, JsonRequestBehavior.AllowGet);
}
And this is my route information. When I test the call without product codes without full stops in them it works as expected and returns the correct JSON data.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
This is my class here.
public class StockWarehouseInfoBins
{
public StockWarehouseInfoBins();
public long? BinItemID { get; set; }
public string BinName { get; set; }
public long? WarehouseItemID { get; set; }
public long? WarehouseID { get; set; }
public string WarehouseName { get; set; }
public decimal ConfirmedQtyInStock { get; set; }
public decimal QuantityReserved { get; set; }
public decimal QuantityAllocatedBOM { get; set; }
public decimal QuantityAllocatedSOP { get; set; }
public decimal QuantityAllocatedStock { get; set; }
}
I am calling the above in a async method. Which bininfo is getting null when the full stop is encourted.
StockWarehouseInfoBins binInfo = new StockWarehouseInfoBins();
binInfo = await restServices.GetWarehouseBinsInformaiton(lblstockCode.Text, SouceWarehouseName);
I presume I need to URL encode the API calls but is there a way to do this on the HTTP client so I don't need to change every call?

asp.net core web api Post form data with brackets

I'm running server build with asp.net core (v2.1) web api and have this REST HTTP call:
POST http://website.com/callback
with the headers:
...
Content-Type: application/x-www-form-urlencoded
and the body:
response%5Bkey%5D=123&response%5Bname%5D=hi
I want to receive this message at this point:
[HttpPost]
[Route("callbacks")]
public ActionResult Hook([FromForm]Model model)
{
// <---- Model has instance but empty fields
return Ok();
}
My Model is:
public class Model
{
public string key { get; set; }
public string name { get; set; }
}
Somehow the brackets ("[key]" and "[name]") are not being parsed into my model instance. They are null both, although I provide them in body.
How to solve it?
You should set name in form for your properties:
public class Model
{
[FromForm(Name = "response[key]")]
public string key { get; set; }
[FromForm(Name = "response[name]")]
public string name { get; set; }
}

Multiple actions were found that match the request: WebAPI 2

I've read a few SO posts and none of them quite cover my scenario so I'm going to post here.
Given the following route config registration:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and these controller actions in a controller that inherits from ApiController:
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{
}
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{
}
public class GetDocumentsRequest
{
public string CorrelationId { get; set; }
public int Id { get; set; }
public string ObjectId { get; set; }
public string BusinessArea { get; set; }
public string UserId { get; set; }
public string SystemName { get; set; }
public string SystemToken { get; set; }
public Letter LetterDetails { get; set; }
public List<KeyValuePair<string, string>> KeyValue { get; set; }
}
public class FinishDocumentsRequest
{
public string CorrelationId { get; set; }
public string[] Documents { get; set; }
}
I thought doing it this way would be enough disambiguation for the IHttpActionSelector to correctly choose the route, but unfortunately it is not.
So my questions is "Is there a way to make this code work correctly, and keep it in the same controller?"
Thank you,
Stephen
You could use attribute routing for this.
Define the route as a string in the Route attribute ontop of the methods as this
[Route("api/controller/Post1")]
[HttpPost]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{
}
[Route("api/controller/Post2")]
[HttpPost]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{
}
The request routing pipeline isn't smart enough to determine if the body of the request matches the parameter type (aka overloading). (The compiler is smart enough, which is why this compiles and you have runtime issues.)
You have a couple of different options.
You can either add an [Route(<ActionName>)] attribute on both of your posts.
Make two controllers, one for GetDocuments and one for FinishDocuments
Make one Post method that is ambiguous. (I'd avoid this)
If you choose option 1, your API uri will have to be .../api/MyController/MyActionName rather than .../api/MyController/. It's also advisable to add [HttpGet] and [HttpPost] attributes on your methods.
Sample:
public class DocumentController : ApiController
{
// POST /api/Document/GetDocuments
[HttpPost]
[Route("GetDocuments")]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request) { ... }
// POST /api/Document/FinishDocuments
[HttpPost]
[Route("FinishDocuments")]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request){ ...}
}
If you choose option 2, you have to maintain an additional code file.
public class GetDocumentsController : ApiController
{
// POST /api/GetDocuments
[HttpPost]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request) { ... }
}
public class FinishDocumentsController : ApiController
{
// POST /api/FinishDocuments/
[HttpPost]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request){ ...}
}
If you choose option 3, may God have mercy on your soul you're going to have a bad time maintaining it.
Add the Route attribute decoration to your web api functions and that will assit the selector to choose the route:
[Route("Post1")]
public GetDocumentsResponse Post([FromBody]GetDocumentsRequest request)
{
}
[Route("Post2")]
public FinishDocumentsResponse Post([FromBody] FinishDocumentsRequest request)
{
}
I also recommend adding the http method decoration such as [HttpPost] or [HttpGet]

Post Error, "Error converting value 'ID' to type" to

I changed a relationship between my Job and CustomerEmployee Class, I am trying to make a Post and I am getting a error. Using a break point I see that the Id's are not making it back to the apiController. I have done some searching and I think it seems it might be a Json issue, not sure how to correct it. I have 6 Id's I am trying to Post but they are setup the same so I will show code for one of them
Error Message
$id: "1"
Message: "The request is invalid."
ModelState: {$id:2, newJob.CustomerEmployeePMId:[,…], newJob.CustomerEmployeeAdminId:[,…],…}
$id: "2"
newJob.CustomerEmployeeAccountantId: [,…]
0: "Error converting value 10 to type 'TexasExteriorSPA.Models.CustomerEmployee'. Path 'CustomerEmployeeAccountantId', line 1, position 150."
newJob.CustomerEmployeeAdminId: [,…]
newJob.CustomerEmployeePMId: [,…]
newJob.CustomerEmployeeSuperintendentId: [,…]
view
<select class="form-control" ng-options="customer.CustomerEmployeeId as customer.CustomerEmployeeFirstName + ' ' + customer.CustomerEmployeeLastName for customer in customerEmployeeArray | filter:{CustomerEmployeeRole : 'Accountant', CustomerId : currentItem.CustomerId} " ng-model="currentItem.CustomerEmployeeAccountantId">
<option value="" selected="selected">Customer Acct</option>
</select>
Angular Controller
//Post New Job
$scope.submitJob = function () {
var data = {
JobId: $scope.JobId,
CustomerEmployeeAdminId: $scope.currentItem.CustomerEmployeeAdminId
}
$http.post('/api/apiJob/PostNewJob', data).success(function (data, status, headers) {
console.log(data);
$scope.openNewJobModal.then(function (m) {
m.modal('hide');
});
});
};
WebApiConfig
// Web API configuration and services
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "JobApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { controller = "apiJob", id = RouteParameter.Optional }
);
apiController
// POST api/<controller>
public async Task<IHttpActionResult> PostnewJob([FromBody]JobViewModel newJob)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var context = new ApplicationDbContext())
{
var job = new Job();
Mapper.CreateMap<JobViewModel, Job>();
Mapper.Map(newJob, job);
context.Jobs.Add(job);
await context.SaveChangesAsync();
return CreatedAtRoute("JobApi", new { job.JobId }, job);
}
}
Job Class
public class Job
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Int64 JobId { get; set; }
public CustomerEmployee CustomerEmployeeAccountantId { get; set; }
}
CustomerEmployee Class
public class CustomerEmployee
{
[Key]
public int CustomerEmployeeId { get; set; }
public string CustomerEmployeeFirstName { get; set; }
public string CustomerEmployeeLastName { get; set; }
public string CustomerEmployeeRole { get; set; }
public Int64? CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
Update, It looks like the Id is not making it out of the Angular Controller
Based on what I can see, I think that Florian has it right. Your code should probably be something like:
public class Job
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Int64 JobId { get; set; }
public int CustomerEmployeeAccountantId { get; set; }
public virtual CustomerEmployee CustomerEmployeeAccountant { get; set;}
}
Your object should have a reference to both the Key (CustomerEmployeeAccountantId) and the object (CustomerEmployeeAccountant). The reason your code is failing is that your DB link is between the Id's, hence the 10 that's trying to be assigned to the property. The "Id" property should be an int.
By adding the additional virtual property, you're telling Entity Framework "Hey, put the Id of the Foreign Key in the Id property and then go ahead and fill out CustomerEmployee object with the associated data based on the Foreign Key relationship."
Then, if you want to access the CustomerEmployeeAccountant data, it should be available in the JSON object returned by your API in a property marked CustomerEmployeeAccountant.
Just be careful. I've noticed that WebApi and EF don't always do so well with recursive objects, so I'd personally dump this data into a ViewModel before it's passed to the client. There are ways to turn it off/change the behaviour, but I'm huge fan of only returning exactly what you need to minimize bandwidth.
Also, just a personal preference, but it seems to be the direction that most of Microsoft's documentation is headed, is to favor long over int64. It's the same data type, but I've noticed the trend of using long for naming consistency. Int64 is technically correct though.
UPDATE:
Looking at your screenshots, trying changing your variable name from var data = {}; to var postData = {};. I can't see the rest of your code, but it looks like there are several variables named data. Could that be causing the conflict?
"Error converting value 10 to type 'TexasExteriorSPA.Models.CustomerEmployee' is the relevant line. It seems like you're passing the value 10 as CustomerEmployeeAdminId. C# now tries to convert your given object to an object of type JobViewModel and fails when it want's to convert 10 to CustomerEmployee. You could do something like this to fix it:
public class Job
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Int64 JobId { get; set; }
public int CustomerEmployeeAccountantId { get; set; }
}

Fiddler - asp.net web api custom POST

I have a Backbone.js application and it's calling one of my WEB API custom POST method. Here is the code to my Custom POST
WebApiConfig.cs
config.Routes.MapHttpRoute
(
name: "UserAdministrationApi",
routeTemplate: "api/{controller}/PostUserInfo/{EmployeeDTO}",
defaults: new
{
EmployeeDTO = RouteParameter.Optional,
controller = "UserAdministration",
action = "PostUserInfo",
});
Controller
[HttpPost]
[ActionName("PostUserInfo")]
public HttpResponseMessage PostUserInfo(EmployeeDTO value)
{
// handling POST
}
EmployeeDTO
public class EmployeeDTO
{
public bool masAccess { get; set; }
public bool reportAccess { get; set; }
public string name { get; set; }
public string office { get; set; }
}
When I try to test this in Fiddler even before going to test it with Backbone.js code, I am getting 500 Internal error. Not sure what's wrong
// FIDDLER TEST
POST : http://localhost:56501/api/useradministration/PostUserInfo
Request Body
{
masAccess:"true"
reportAccess:"false"
}
Thank you for the help
A few things:
The EmployeeDTO should not be part of the routeTemplate, as Andrei mentioned.
Your request body is not valid JSON according to JSONlint.com. Try
{
"masAccess": "true",
"reportAccess": "false"
}
Also, you may need to add the header Content-Type: application/json to your Fiddler request.

Categories

Resources