Get by id as well as by string angularjs webapi - angularjs

I am completely new with angularjs using with webapi and I am probably going about it the wrong way but basically I want to search for a product by text(as I am executing the query in the database) as well as get a product by id for the purpose of updating the existing product.
The search by text I do as follow.
//productResource.js
(function () {
"use strict";
angular.module("common.services").factory("productResource", ["$resource", "appSettings", productResource])
function productResource($resource, appSettings) {
return $resource(appSettings.serverPath + "/api/products/:search");
}
}());
And in my webApi controller
public IEnumerable<Product> Get(string search)
{
var repository = new ProductRepository();
return repository.Restrieve(search);
}
public Product Get(int id)
{
Product product;
var repository = new ProductRepository();
if (id > 0)
{
product = repository.GetProductById(id);
}
else
{
product = repository.CreateProduct();
}
return product;
}
And then in my WebApiConfig:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{search}",
defaults: new { search = RouteParameter.Optional }
);
The way it is setup now I manage to do searches by text.
How can I configure productResource.js and WebApiConfig to making provision for a search by id as well?

I would go with a slightly different routes here. In a RESTful API you have resources (products in your case). A resource is uniquely identified by id. So I would have the following route:
GET /products/:id
and if I wanted to search multiple products by text:
GET /products?search=xxxx
which would be just fine with the default routes:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and now on the client side:
function productResource($resource, appSettings) {
return $resource(appSettings.serverPath + 'api/products/:id');
}
and to query:
productResource.query({ id: '123'});
productResource.query({ search: 'some search text'});
Here's a nice overview with examples of $resource.
Also make sure you have read the following blog post before the next time you try to put search texts (or any arbitrary data coming from clients) in the path portion of your routes instead of where they belong -> the query string.

Related

WebAPI URI goes wrong

I have created angularjs scripts to call web api. My API config is as below,
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
I am currently in the view <li>#Html.ActionLink("Create", "GB", "ProjectCreate" , new { area = "" }, null)</li> and from this page I am calling an api api/Users/GetUser but it throws an error that the api is not found and when I check in debug mode, the api being hit is of the uri ProjectCreate/api/Users/GetUser.
This is quite weird and I am not getting where the projectcreate appears from here.
it might be the name of your webapplication. Let me know how are you running your app.

AngularJS hash url and MVC routing

I am facing issue in navigating to URL. My default page set to Login and my application URL is http://localhost:12345/#/.
This works well but there are two ways the user can login to application
Direct through the application.
Getting username and password trough query string.
When application is logging through Query String the url is like http://localhost:12345?auth=123654654656564/#/.
I would like to remove auth value from the URL. I tried to map the routing but it doesn't work.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Account", action = "Login"}
);
And also i tried to create one more action result that will return the view
routes.MapRoute(
name: "ActualDefault",
url: "{controller}/{action}",
defaults: new { controller = "Account", action = "LoginQuery" }
);
Controller:
public ActionResult Login()
{
if (Request.QueryString.Count > 0 && Request.QueryString != null)
{
//validating
return RedirectToAction("LoginQuery", "Account");
}
else
{
return View();
}
}
public ActionResult LoginQuery()
{
return View("Index");
}
The above code removes query string but the URL will be http://localhost:12345/Account/LoginQuery/#/.
I just need the URL like http://localhost:12345/#/.
Logging in via Query String
I would be negligent not to point out that this is an extremely bad practice. You should always use HTTP POST when logging into an application and send the user secrets in the body of the post, not the query string.
See
Handling Form Edit and Post Scenarios
Submit Form with Parameters in ASP.NET MVC
BUILDING ASP.NET MVC FORMS WITH RAZOR
Note that you can also create forms in plain HTML (or via angularjs) to call an MVC action method, or you can make an HTTP POST via JavaScript or some other programming language to do the same thing.
Query string values are completely ignored by MVC routing. But you can make a custom route use query string values.
public class LoginViaQueryStringRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var path = httpContext.Request.Path;
if (!string.IsNullOrEmpty(path))
{
// Don't handle URLs that have a path /controller/action
return null;
}
var queryString = httpContext.Request.QueryString;
if (!queryString.HasKeys())
{
// Don't handle the route if there is no query string.
return null;
}
if (!queryString.AllKeys.Contains("username") && !queryString.AllKeys.Contains("password"))
{
// Don't handle the case where controller and action are missing.
return null;
}
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values["controller"] = "Account";
routeData.Values["action"] = "LoginQuery";
routeData.Values["username"] = queryString["username"];
routeData.Values["password"] = queryString["password"];
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
Usage
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new LoginViaQueryStringRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
This route will now match http://localhost:12345/?username=foo&password=bar and send it to your LoginQuery action method.
Logging in via http://localhost:12345/#/
It is unclear how you expect this to work. Since everything after the hash tag are generally not sent to the server from the browser, http://localhost:12345/#/ is equivalent to http://localhost:12345/. So, you are effectively saying "I want my home page to be the login page".
In a typical MVC application, you would setup an AuthorizeAttribute on the home page to redirect the user to the login page. After the user logs in, they would be redirected back to the home page (or usually whatever secured page they initially requested).
[Authorize]
public ActionResult Index()
{
return View();
}
If you want all of your application secured, you can register the AuthorizeAttribute globally and use AllowAnonymousAttribute on your public action methods (such as the login and register pages).
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AuthorizeAttribute());
filters.Add(new HandleErrorAttribute());
}
}
And your login action methods:
[AllowAnonymous]
public ActionResult Login()
{
//...
}
[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel model)
{
//...
}
[AllowAnonymous]
public ActionResult LoginQuery(string username, string password)
{
//...
}
But then, that is a typical MVC-only application.
If you are using Angular to make a SPA, then this could be a very different story. Namely, you would probably switch views on the client side without doing an HTTP 302 redirect to the login form (perhaps it would be a popup - who knows). The point is, without any details of how the client is setup to communicate with MVC, it is not possible to give you any useful advice on setting up MVC for your client beyond how you would typically setup MVC to work in a multi-page application.
NOTE: I can tell you that your routing is misconfigured. The Default and ActualDefault definitions cannot exist in the same route configuration because the first match always wins, therefore the first one will run and the other one will never run. Both of the route URL definitions will match any URL that is 0, 1, or 2 segments in length, so whichever you have first in the route table will match and the other one will be an unreachable execution path.

What is the best way to retrieve data using Web API 2 and AngularJS

I am just learning on how to use Web API with angular but I am having a few issues on retrieving data and I would appreciate it if someone could help me.
I have a controller called GetUserController with the following methods.
public class GetUserController : ApiController
{
[HttpGet]
public string GetUserName()
{
var user = "John Doe"
return user;
}
[HttpGet]
public string GetUserName2()
{
string user = "Jane Doe";
return user;
}
}
//Angular side of things
function getUser() {
return $http.get('/api/GetUser').then(function (data) {
return data;
});
}
The above code works fine and and returns the first user from the controller . however when I try to get the second by using the below angular code:
function getUser() {
return $http.get('/api/GetUser/GetUserName2').then(function (data) {
return data;
});
}
This does not work for some reason it says it can't find the GetUserName2 method. Am I missing something ? Please help?
EDIT: The error i'm getting is : Multiple actions were found that match the request
As #shammelburg has pointed out, this is as a result of Web.API not being able to match your request to a controller/method.
It's not so much that it's not RESTful, and has nothing to do with the verb you are using... it's just that an appropriate route map was not found.
Per the accepted answer, you can add another generic route map to enable the method of access you are attempting, however a more specific option exists using attribute routing:-
public class GetUserController : ApiController
{
[Route("api/getuser")]
[HttpGet]
public string GetUserName()
{
var user = "John Doe"
return user;
}
[Route("api/getuser/getusername2")]
[HttpGet]
public string GetUserName2()
{
string user = "Jane Doe";
return user;
}
}
And to enable the use of attribute routes, add this to your WebApiConfig class:-
config.MapHttpAttributeRoutes();
This method allows you to setup specific custom mappings of URLs to controllers/methods at the individual method level, without having to make a global route map that may conflict with something else in your application at a later date.
You can find more info on attribute routing here
Whilst the above will resolve the specific issue you are having, there would in practice be a different way to implement the example you gave:-
public class GetUserController : ApiController
{
[Route("api/user/{id}")]
[HttpGet]
public string GetUserName(int id)
{
// this would be replaced with some sort of data lookup...
var user = "unknown";
if (id == 1) {
user = "John Doe";
} else if (id == 2) {
user = "Jane Doe";
} // and so on...
return user;
}
}
In the above, the URL api/user/x where x is a number, e.g. api/user/1 will match the GetUserName method of the GetUserController and pass the number as an argument to the method.
This would be accessed using something like this in Angular:-
function getUser(id) {
return $http.get('/api/user/' + id).then(function (data) {
return data;
});
}
This is caused because it is not a true RESTful call which use HTTP verbs, GET, POST, PUT, DELETE.
The way to get your code to work is by altering your WebApiConfig.cs file.
From:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
To:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
As you can see we've added the {action} to the routeTemplate which makes this very much like a MVC Controller.
This allows you to call your API methods (GetUserName & GetUserName2) name like you are trying to do in your angular $http function.
Example:
return $http.get('/api/GetUser/GetUserName')
return $http.get('/api/GetUser/GetUserName2')

AngularJS web api call - can't find get method on controller

I cannot for the life of me understand why this isn't working.
I have a simple ASP.Net MVC Web API controller, with 2 get methods. I have an AngularJS service with 2 corresponding functions. The GetAllRisks works perfectly well. However, the GetRiskByID comes back with an error saying "No HTTP request was found that matches the request "http://localhost:49376/api/RiskApi/GetRiskByID/6" and "No action can be found on the RiskApi controller that matches the request."
The URL is being passed correctly. I have tried various options for the API routing but can't get anywhere. I am sure I am missing something simple but can't see it.
I would really appreciate any thoughts.
Thanks,
Ash
RiskApiController
public class RiskApiController : ApiController
{
private readonly IRiskDataService _riskDataService;
public RiskApiController(IRiskDataService riskDataService)
{
_riskDataService = riskDataService;
}
// GET api/RiskApi
[HttpGet]
public IEnumerable<IRisk> GetAllRisks()
{
return _riskDataService.GetAllRisks().Take(20);
}
// GET api/RiskApi/5
[HttpGet]
public IRisk GetRiskByID(int riskID)
{
IRisk risk = _riskDataService.GetRiskByID(riskID);
if (risk == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return risk;
}
}
service.js
app.service('OpenBoxExtraService', function ($http) {
//Get All Risks
this.getAllRisks = function () {
return $http.get("/api/RiskApi/GetAllRisks");
}
//Get Single Risk by ID
this.getRisk = function (riskID) {
var url = "/api/RiskApi/GetRiskByID/" + riskID;
return $http.get(url);
}
});
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ActionRoute",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Try changing your WebApiConfig class to:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
And change your param: riskID to id.
// GET: api/RiskApi/GetRiskByID/5
[HttpGet]
public IRisk GetRiskByID(int id)
{
IRisk risk = _riskDataService.GetRiskByID(id);
if (risk == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return risk;
}
Then, you could use:
// GET: api/RiskApi/GetAllRisks
// GET: api/RiskApi/GetRiskByID/5
...try junking your config's HttpRoute Mapping and introduce the route mapping with Attributes.
Since this is first-gen Web API, you'll need to pull in the AttributeRouting.WebAPI NuGet package, and you'll likely want to refer here(strathweb) for straightforward guidance on how to implement.
Note that you have a mapping problem in your current implementation on your {id} parameter: you declare it to be id in the route configuration, but then you identify it as riskID inside the controller's method; these need to match. Switch your controller's method to have its incoming routeParameter be named id. You could optionally switch your config to declare the {riskID} parameter in your routes, but that would couple your global configuration to the semantics of a specific controller and you'd likely need to implement more routing constraints to have other controllers not named "Risk" make sense.
public class RiskApiController : ApiController
{
private readonly IRiskDataService _riskDataService;
public RiskApiController(IRiskDataService riskDataService)
{
_riskDataService = riskDataService;
}
// GET api/RiskApi
[HttpGet]
[Route("api/RiskApi")]
public IEnumerable<IRisk> GetAllRisks()
{
return _riskDataService.GetAllRisks().Take(20);
}
// GET api/RiskApi/5
[HttpGet]
[Route("api/RiskApi/{id}")]
public IRisk GetRiskByID(int id)
{
IRisk risk = _riskDataService.GetRiskByID(id);
if (risk == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return risk;
}
}

AngularJS web API not working with strings

At the moment what I am trying to do is to return a list of strings from my web API, but I was getting this error 'No 'Access-Control-Allow-Origin' header is present on the requested resource.'
After struggling for a while I have realised it's because it will only allow me to return a list of integers and not a list of strings.
Here are the two methods (one of which would obviously be commented out).
public IEnumerable<string> Get(int userId)
{
IEnumerable<string> listOfFormGroups = new List<string>() { "Form Group 1", "Form Group 2", "Form Group 3" };
return listOfFormGroups;
}
public IEnumerable<int> Get(int id)
{
IEnumerable<int> listOfRoles = new List<int>() { 1, 2, 3 };
return listOfRoles;
}
The top method throws the error, but the second method returns fine.
my angular service
this.get = function (userId) {
return $http.get(toApiUrl('formgroup/') + userId);
}
my angular controller calling the service,
var promiseGet = formGroupService.get(1);
promiseGet.then(function (result) {
$scope.FormGroups = result.data
},
function (errorResult) {
$log.error('Unable to get users form groups. An error has occurred. :', errorResult);
});
If you look at the default WebApiConfig, it looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I believe the integer version is working because you called the param id as stated in the config. The string one you have is trying to map to id, seeing userId as the only parameter and not mapping it correctly. I believe that is your error.
Change your method to look like this:
public IEnumerable<string> Get(int id)
{
IEnumerable<string> listOfFormGroups = new List<string>() { "Form Group 1", "Form Group 2", "Form Group 3" };
return listOfFormGroups;
}
peinearydevelopment's answer may be correct, but there is another interesting part of the question. The No 'Access-Control-Allow-Origin' message is a CORS error message (a cross origin request). In general, a script is limited to making AJAX requests to the same URL origin that it came from.
My guess that part of the problem is coming from the fact that toApiUrl() creates a URL that is of a different domain than the script came from.
There are ways around this, but it requires changes on the server side. See the documentation on how to do this.
It does't have any problem. You can call it by having below url.
I have already tested it and its working fine.
http://localhost:52257/api/temp/Get?userId=3
// please use your domain or localhost....
So in your case you can go with,
return $http.get(toApiUrl('formgroup/Get?userId=3'));

Resources