AngularJS - Mapping REST search endpoint onto $resource - angularjs

ng-resource returns an object with the following default resource actions
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };
I'm not sure exactly the best method for querying data from REST WebApi endpoints from AngularJS, but I've implemented Predicate Builder server-side in order to query my db using Linq. I have a (POST) endpoint named "Search()" #/api/Product/Search that will accept a searchCriteria JSON object which is deserialized, fed to the Linq predicate builder, and executed against the dbContext. My WebApi2 controller is structured like this, using the new route attribute feature:
[RoutePrefix("Product")]
public class ProductController : ApiController
{
[HttpGet]
[Route("")]
public IEnumerable<Product> Get()
{
try
{
return _productBL.Get();
}
catch
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
[HttpGet]
[Route("{productId}")]
public Product Get(string productId)
{
try
{
var product= _externalWorkStepBL.GetById(productId);
if (product== null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return product;
}
catch (Exception)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
[HttpPost]
public HttpResponseMessage Post([FromBody]Product product)
{
try
{
_productBL.Insert(product);
var response = Request.CreateResponse(HttpStatusCode.Created, product);
response.Headers.Location = new Uri(Request.RequestUri, string.Format("Product/{0}", product.workItemID));
return response;
}
catch
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
}
[HttpPost]
[Route("Search")]
public IEnumerable<Product> Where([FromBody] SearchCriteria searchCriteria)
{
if (searchCriteria == null || (searchCriteria.FieldContainsList == null || searchCriteria.FieldEqualsList == null || searchCriteria.FieldDateBetweenList == null))
{
throw new HttpRequestException("Error in, or null, JSON");
}
return _productBL.Where(searchCriteria);
}
[HttpPut]
[Route("")]
public HttpResponseMessage Put([FromBody]Productproduct)
{
try
{
_productBL.Update(product);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Headers.Location = new Uri(Request.RequestUri, string.Format("Product/{0}", product.Id));
return response;
}
catch ()
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
[HttpDelete]
[Route("{productId}")]
public void Delete(string productId)
{
HttpResponseMessage response;
try
{
_productBL.Delete(productId);
response = new HttpResponseMessage(HttpStatusCode.NoContent);
response.Headers.Location = new Uri(Request.RequestUri, string.Format("Product/"));
}
catch (Exception)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
}
On the client side I've wrapped $resource in a factory called $myResource, adding a PUT method. I then use $myResource for my other factories as follows:
var app = angular.module('App', ['ngResource'])
.factory('$myResource', ['$resource', function ($resource) {
return function (url, paramDefaults, actions) {
var MY_ACTIONS = {
'update': { method: 'PUT' }
};
actions = angular.extend({}, MY_ACTIONS, actions);
return $resource(url, paramDefaults, actions);
}
}])
.service('ProductFactory', ['$myResource', function ($myResource) {
return $myResource('/api/Product/:productId')
}]);
This works great, but now I wish to add my Search endpoint. The Angular documentation for ng-Resource states that a url can be overridden in the action method, but it's not clear to me how to do this. I'm able to add the "search" action to $myResource, but how do I modify the url in the ProductFactory?
.factory('$myResource', ['$resource', function ($resource) {
return function (url, paramDefaults, actions) {
var MY_ACTIONS = {
'update': { method: 'PUT' },
'search': { method: 'POST','params': { searchCriteria: '#searchCriteria' }, isArray: true }
};
actions = angular.extend({}, MY_ACTIONS, actions);
return $resource(url, paramDefaults, actions);
}
}])
As it currently is, calling ProductFactory.search(searchCriteria) sends a POST request with the correct JSON, but to the wrong url, "/api/Product". I need it to post to "/api/Product/Search". How can I modify $myResource to use "api/xxx/Search" where xxx is the controllername?

Nevermind! Didn't expect this to work, but it does.
.factory('$myResource', ['$resource', function ($resource) {
return function (url, paramDefaults, actions) {
var searchUrl = url + "/Search/:searchCriteria"
var MY_ACTIONS = {
'update': { method: 'PUT' }, //add update (PUT) method for WebAPI endpoint
'search': { method: 'POST', url : searchUrl,'params': { searchCriteria: '#searchCriteria' }, isArray: true } //add Search (POST)
};
actions = angular.extend({}, MY_ACTIONS, actions);
return $resource(url, paramDefaults, actions);
}
}])

Related

Angular - Web api routing put method

I have a little problem with routing in my app Angular + wep api. I want to put user, and it doesn't work. Server returns 404 and error:
Failed to load resource: the server responded with a status of 404 (Not Found)
and
Message":"No HTTP resource was found that matches the request URI 'http://localhost:53544/api/Users/PutUser/1'.","MessageDetail":"No action was found on the controller 'Users' that matches the name 'PutUser'."
It's strange because the method exists.
The entirety of my code follows:
My route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
My put method:
[HttpPut]
[ActionName("PutUser/{userId}")]
[ResponseType(typeof(void))]
public IHttpActionResult PutUser(int userId, User user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (userId != user.Id)
{
return BadRequest();
}
db.Entry(user).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!UserExists(userId))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
My angular put method:
this.PutUser = function (userId, user) {
var promise = $http({
method: 'PUT',
url: 'api/Users/PutUser/' + userId,
data: user
})
.then(function (response) {
return "update";
},
function (response) {
return response.statusText;
});
return promise;
}
Specify the [FromUri] and [FromBody] to define parameter mapping
[HttpPut]
[ActionName("PutUser/{userId}")]
[ResponseType(typeof(void))]
public IHttpActionResult PutUser([FromUri]int userId, [FromBody]User user)
{
You have to make sure that the post request HTTP header contains
Content-Type: application/x-www-form-urlencoded

MVC CORE web application Angularjs http$ not returning data to api call

In my controller i have the following
[HttpPost]
public string AddEmployee(Employee Emp)
{
if (Emp != null)
{
_context.Employees.Add(Emp);
_context.SaveChanges();
return "Employee Updated";
}
else
{
return "Invalid Employee";
}
}
When i use the following in my angularjs factory no data is passed.
EmployeeService.AddEmp = function (employee) {
console.log(employee);
var response = $http({
method: "post",
url: "/Home/AddEmployee",
data: JSON.stringify(employee),
dataType: "json"
});
return response;
}
but when i use the following data is passed. What am i doing wrong with the above code.
EmployeeService.AddEmp = function (employee) {
console.log(employee);
var response = $http({
method: "post",
url: "/Home/AddEmployee",
params: {
employeeCode: JSON.stringify(employee.employeeCode),
firstName: JSON.stringify(employee.firstName),
lastName: JSON.stringify(employee.lastName),
}
});
return response;
}
I think you'll need to add something like this:
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
to WebApiConfig.Register

Parameters on controller always null when submitted via Angular

I'm creating a js object with the same properties as my controller action expects as parameters.
controller.js
(function () {
'use strict';
angular
.module('app')
.controller('requestController', requestController);
requestController.$inject = ['$scope', 'lecturesFactory', 'attendeesFactory'];
$scope.setSelectedLectures = function () {
var lecture1, lecture2;
for (var i = $scope.lectures.length - 1; i >= 0; i--) {
var lecture = $scope.lectures[i];
if (lecture.selected === true) {
if (lecture1 == null) {
lecture1 = lecture.lectureId;
}
else {
lecture2 = lecture.lectureId;
}
}
}
attendeesFactory.setSelectedLectures($scope.emailAddress.text, lecture1, lecture2).then(function (data) {
$scope.showInvalidUserMessage = true;
$scope.message = data.message;
});
};
activate();
function activate() { }
}
})();
attendessFactory.js
(function () {
'use strict';
angular
.module('app')
.factory('attendeesFactory', attendeesFactory);
attendeesFactory.$inject = ['$http'];
function attendeesFactory($http) {
var service = {
setSelectedLectures: setSelectedLectures
};
return service;
function setSelectedLectures(emailAddress, lecture1, lecture2) {
var promise = $http({
method: 'POST',
url: '/Home/SetSelectedLectures',
data: {
emailAddress: emailAddress,
lecture1: lecture1,
lecture2: lecture2
}
}).then(function (response) {
console.log(response);
return response.data;
});
return promise;
}
}
})();
And my MVC Controller:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult SetSelectedLectures(SelectedLectureData data)
{
// ...
}
}
public class SelectedLectureData
{
public String EmailAddress { get; set; }
public int Lecture1 { get; set; }
public int? Lecture2 { get; set; }
}
I've tried what some posts on StackOverflow suggested, such as using JSON.stringify, changing the content-type, but I still get the parameter values null (even if I put them directly in the action, instead of using a custom class).
Use [FromBody] anotation to make it working which will serialize data in SelectedLectureData model.
public IActionResult SetSelectedLectures([FromBody]SelectedLectureData data)
Otherwise you need to do
var promise = $http({
method: 'POST',
url: '/Home/SetSelectedLectures',
data: JSON.strigify({ "data": {
emailAddress: emailAddress,
lecture1: lecture1,
lecture2: lecture2
}})
})
Try updating your javascript to
function setSelectedLectures(emailAddress, lecture1, lecture2) {
var model = {
emailAddress: emailAddress,
lecture1: lecture1,
lecture2: lecture2
};
var data = JSON.strigify(model);
var promise = $http({
method: 'POST',
url: '/Home/SetSelectedLectures',
data: data
}).then(function (response) {
console.log(response);
return response.data;
});
return promise;
}
and using [FromBody] attribute on controller action
[HttpPost]
public IActionResult SetSelectedLectures([FromBody]SelectedLectureData data)
{
// ...
}
JSON property name should be same as class properties else it will take it as null
function setSelectedLectures(emailAddress, lecture1, lecture2) {
var promise = $http({
method: 'POST',
url: '/Home/SetSelectedLectures',
data: {
EmailAddress : emailAddress,
Lecture1: lecture1,
Lecture2: lecture2
}
}).then(function (response) {
console.log(response);
return response.data;
});

angular.js:13424 TypeError: $scope.entry.update is not a function. Updating the correct way with angularjs

I am trying to make an update to an existing object but get the following error $scope.entry.update is not a function.
I created a service called 'budgetResource'
"use strict";
angular.module("common.services").factory("budgetResource", ["$resource", "appSettings", budgetResource])
function budgetResource($resource, appSettings) {
return $resource(appSettings.serverPath + "api/budget/:id", null,
{
'update': { method: 'PUT', isArray: true },
'delete': { method: 'DELETE', isArray: true },
'save': { method: 'POST', isArray: true }
});
}
Herewith the function in my controller where budgetResource service is injected with the function $scope.updateBudgetAmount being called.
$scope.updateBudgetAmount = function (categoryId) {
$scope.entry = new budgetResource();
$scope.entry = {
"budgetAmount": $scope.budgetAmount,
"categoryId": categoryId
}
$scope.entry.update({ id: categoryId },
function (data) {
$scope.categories = data;
$scope.category = "";
},
function (error) {
$scope.message = error.statusText;
});
}
which in turn calls the webapi method
public IHttpActionResult Put(int id, [FromBody]Category cat)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
BudgetRepository repo = new BudgetRepository();
var categories = repo.SaveCategory(cat);
return Ok(categories);
}
How can modify this so that it is dine correctly?
After you do $scope.entry = {...},$scope.entry becomes a plain javascript object, so $scope.entry.update is not exist.

AngularJS $Resource - Callback always triggers the error function even though server returns a HTTP 200 response

I consume a REST service using $resource. Why is it that the error callback function is always triggered even though I get a Http: 200 (Ok) response from the server? I've tried 2 ways of setting up the callback functions and both have the same issue.
Here is the Angular controller where I consume the service:
appRoot
.controller(
'BatchTaskController',
['$scope', 'batchTaskService', function ($scope, batchTaskService){
$scope.runImportIntermediariesTask = function () {
batchTaskService.runImportIntermediariesTask().$promise.then(
function (value) { alert('success') },
function (error) { alert('error') }
);
};
$scope.runImportVantageTransactionsTask = function () {
batchTaskService.runImportVantageTransactionsTask(
function () { alert('success'); },
function () { alert('error'); }
);
};
$scope.runProcessVantageTransactionsTask = function () { batchTaskService.runProcessVantageTransactionsTask(); };
}]);
Here is the Angular service:
var angularVectorServices = angular.module('angularVector.services', ['ngResource']);
angularVectorServices.factory('batchTaskService', ['$resource' , function ($resource) {
return $resource(
"http://localhost:58655/api/BatchTask/:action",
{
action: "#action"
},
{
runImportIntermediariesTask: {
method: "POST",
params: {
action: "RunImportIntermediariesTask"
}
},
runImportVantageTransactionsTask: {
method: "POST",
params: {
action: "RunImportVantageTransactionsTask"
}
},
runProcessVantageTransactionsTask: {
method: "POST",
params: {
action: "RunProcessVantageTransactionsTask"
}
}
}
);
}]);
I am using WebApi. Here is the Server ApiController Code:
public HttpResponseMessage RunImportIntermediariesTask()
{
// _importIntermediariesTask.Run();
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
public HttpResponseMessage RunImportVantageTransactionsTask()
{
//_importVantageTransactionsTask.Run();
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Take this as sample, try to make your action as this one below, setting up your code response code on HttpStatusCode.Whatever:
public HttpResponseMessage GetUser(HttpRequestMessage request, int userId, DateTime lastModifiedAtClient)
{
var user = new DataEntities().Users.First(p => p.Id == userId);
if (user.LastModified <= lastModifiedAtClient)
{
return new HttpResponseMessage(HttpStatusCode.NotModified);
}
return request.CreateResponse(HttpStatusCode.OK, user);
}
Hope this helps.

Resources