I have a database with two tables. Countries and Cities where each city has a relation to a spesific country.
In my ASP.Net Web API I can get a list of countries by a GET request to http://example.com/api/countries to run the CountriesController. And I can get details about a country by http://example.com/api/countries/1.
If I want a list of all cities for a country the REST query URL should be http://example.com/api/countries/1/cities? And details for a City http://example.com/api/countries/1/cities/1
How can I accomplish this in ASP.Net Web API?
How about this, in global.asax.cs define an additional api route like so:
routes.MapHttpRoute(
name: "CityDetail",
routeTemplate: "api/countries/{countryid}/cities/{cityid}",
defaults: new { controller = "Cities" }
);
Then define a new CitiesController like so:
public class CitiesController : ApiController
{
// GET /api/values
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}
// GET /api/values/5
public string Get(int countryId, int cityid)
{
return "value";
}
// POST /api/values
public void Post(string value)
{
}
// PUT /api/values/5
public void Put(int countryId, int cityid, string value)
{
}
// DELETE /api/values/5
public void Delete(int countryId, int cityid)
{
}
}
Needless to say you might want to improve the controller implementation a bit :)
Related
I am struggling to make Route Prefix working or probably have misunderstanding about Routes in ASP.NET Web API
Here is my code:
public class TestController : ApiController
{
// GET: api/Test
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET: api/Test/5
public string Get(int id)
{
return "value";
}
[HttpGet]
[Route("GetValue")]
public string GetValue(int id)
{
return "best";
}
[HttpGet]
[Route("GetValue1")]
public string GetValue1(int test)
{
return "GetValue";
}
}
Here is the status of each call
localhost:52154/api/test/Getvalue1/1 (HTTP Error 404.0 - Not Found)
localhost:52154/api/test/Getvalue1?test=1 (
The webpage cannot be found )
localhost:52154/api/test/Getvalue?id=1 (Works return "value" but does not call the GetValue Method)
localhost:52154/api/test/1 (Works return "value")
localhost:52154/api/test/ (Works return "value1","value2")
Please let me know what to do to make the GetValue1 and GetValue route work. I did update the NuGet pacakge for ASP.NET to Web Api 2.2
WebApiConfig looks like this
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
Here is the solution which worked for me
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}
// GET: api/Test/5
public string Get(int id)
{
return "value";
}
[HttpGet]
public string GetValue(int id)
{
return "best";
}
[HttpGet]
public string RetriveValue(int test)
{
return "GetValue";
}
courtesy of
How to bypass the exception multiple actions were found in ASP.NET Web API
Use This : [ActionName("Getvalue1")]
hope this works!!
My Web API has two methods hooked up to a repository.
When I make a call to
"api/Cust/GetCustomers"
the full list of customers in my database is being returned. This is fine. As a heads up, i'm using Northwind so the IDs for a Customer are a group of letters. eg - ALFKI or ANTON
When I make a call to a specific CustomerID, for example
"api/Cust/GetCustomers/alfki"
I don't get an error, but the same list from above(containing all customers in the database) is returned. I'm finding this strange because my impression would be that i'd get a not found error if something is incorrect in my controller or repository.
Does anybody with experience know how something like this happens.
I have an already completed example to work off of, and in that example navigating to a specific will return records only for that customer, which is what i'm looking to do.
Here is the code in my api controller, which is almost identical
I'm thinking there must be something subtle in the routing configs that could cause this without causing an error
CustomersAPIController.cs
public class CustomersAPIController : ApiController
{
//
// GET: /CustomersAPI/
private INorthwindRepository _repo;
public CustomersAPIController(INorthwindRepository repo)
{
_repo = repo;
}
//This routing doesn't work, but if it is a possible issue,
the call for a specific customer wasn't working before I added it
[Route("api/Cust/GetOrders({id})")]
public IQueryable<Order> GetOrdersForCustID(string id)
{
return _repo.GetOrdersForCustID(id);
}
[Route("api/Cust/GetCustomers")]
public IQueryable<Customer> GetAllCustomers()
{
return _repo.GetCustomers();
}
[HttpGet]
[Route("api/Cust/GetCustomers/alfki")]
public Customer GetCustomerByID(string id)
{
Customer customer = _repo.GetCustomerByID(id);
return customer;
}
//===========================================
protected override void Dispose(bool disposing)
{
_repo.Dispose();
base.Dispose(disposing);
}
}
and here is my repo
repo.cs
public interface INorthwindRepository:IDisposable
{
//private northwndEntities _ctx = new northwndEntities();
IQueryable<Customer> GetCustomers();
IQueryable<Customer> TakeTenCustomers();
Customer GetCustomerByID(string id);
IQueryable<Order> GetOrders();
IQueryable<Order> GetOrdersForCustID(string id);
Order FetchOrderByID(int orderID);
}
public class NorthwindRepository : INorthwindRepository
{
northwndEntities _ctx = new northwndEntities();
public IQueryable<Customer> GetCustomers()
{
return _ctx.Customers.OrderBy(c => c.CustomerID);
}
public IQueryable<Customer> TakeTenCustomers()
{
var foo = (from t in _ctx.Customers
select t).Take(10);
return foo;
}
public IQueryable<Order> GetOrdersForCustID(string id)
{
var orders = _ctx.Orders.Where(x => x.CustomerID == id).OrderByDescending(x=>x.OrderDate).Take(4);
return orders;
}
public Customer GetCustomerByID(string id)
{
return _ctx.Customers.Find(id);
}
public void Dispose()
{
_ctx.Dispose();
}
Here is a link to a screenshot of the url in my example to work off of, working as intended and returning the records for a specific ID
http://postimg.org/image/oup88k83f/
In this second one, it is a link to my api that I have been basing on my example to work from.
http://postimg.org/image/858t1oph9/
As mentioned above, the code is nearly identical, except for some small changes to the routing and maybe the api controller names.
If anyone has any idea what is causing this, all suggestions are appreciated.
Thank you
*Update fixed a typo in my code
My routeconfig.cs (the same as the template provided my MVC4 API selection when creating a new project)
public class RouteConfig
{
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 }
);
}
}
please, fixed the Route for the action GetCustomerById, look:
[Route("api/Cust/GetCustomers/{id}")]
public Customer GetCustomerByID(string id)
I'm doing my first steps in asp.net mvc trying to develop web api.
I have the following routing function:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "cdApiDefault",
url: "api/{controller}/{action}/{token}/{mid}/{id}",
defaults: new {
token = UrlParameter.Optional,
mid = UrlParameter.Optional,
id = RouteParameter.Optional }
);
}
and the following controller:
namespace cdapi.Controllers
{
public class PostsController : ApiController
{
// GET api/posts
public IEnumerable<string> Get()
{
return new string[] { "GET_value1", "GET_value2" };
}
// GET api/posts/5
public string Get(int id)
{
return "value!!!!!!!!!!!!!!!!!";
}
// POST api/posts
public void Post([FromBody]string value)
{
}
// PUT api/posts/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/posts/5
public void Delete(int id)
{
}
public String GetTest(String token, String mid)
{
return token + " - " + mid;
}
}
}
the following call
hxxp://localhost:52628/api/posts/5
(in my browser) yields some result, i.e., the function GET is being called and return a value.
However, when I try
hxxp://localhost:52628/api/posts/GetTest/MyTestToken/myTestMid
comes back with 'the resource can not be found' error message.
I thought that the {Action} should contain the function to call and that the 'token' and 'mid' should contain the values I specify. What do I do wrong?
ApiControllers work differently than regular MVC Controllers. Here, method names (GET, POST, PUT, DELETE) represent HTTP VERBS, not url fragment. In your first call,
/api/posts/5
this invokes Get(int).
To do routing like you want, switch to standard MVC by inheriting from System.Web.Mvc.Controller instead of System.Web.Http.ApiController and modify your methods to return ActionResult
I have a MVC Web API get method that I'd like to be able to pass a nullable Guid as a parameter. If I setup the GET with a "?Id=null" I get a 400 response. I can pass a empty guid but that I'd rather not do that.
No matter what I change the URI to, "id=, id=null etc" it won't accept null. Does anyone know how to make this work?
[HttpGet]
public User Get(Guid? Id)
Update Route config
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Full Http Get signature, sourceId is the param that id like to pass as null.
[HttpGet]
public IEnumerable<ActionItemsListViewModel> GetPagedList(int skip, int take, int page, int pageSize, [FromUri]List<GridSortInfo> sort, [FromUri] ActionItem.ActionItemStatusTypes? actionItemStatus, Guid? sourceId)
Found the problem, this filter was saying the ModelState was invalid.
public class ApiValidationActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid )
{
var errors = actionContext.ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => e.Value.Errors.First().ErrorMessage).ToList();
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, string.Join(" ", errors));
}
}
}
Try to use:
[HttpGet]
public User Get(Guid? Id = null)
I was able to pass null to the Guid? when I use
query string parameter: api/values?id=null
route parameter: api/values/null
Controller:
public class ValuesController : ApiController
{
public User Get(Guid? Id)
{ ... }
}
I have a ListItem class that is used to represent menu items in my application:
public class ListItem : Entity
{
public virtual List List { get; set; }
public virtual ListItem ParentItem { get; set; }
public virtual ICollection<ListItem> ChildItems { get; set; }
public int SortOrder { get; set; }
public string Text { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public string Area { get; set; }
public string Url { get; set; }
}
I use this data to construct the routes for the application, but I was wondering if there was a clean way to handle controller/views for static content? Basically any page that doesn't use any data but just views. Right now I have one controller called StaticContentController, which contains a unique action for each static page that returns the appropriate view like so:
public class StaticContentController : Controller
{
public ActionResult Books()
{
return View("~/Views/Books/Index.cshtml");
}
public ActionResult BookCategories()
{
return View("~/Views/Books/Categories.cshtml");
}
public ActionResult BookCategoriesSearch()
{
return View("~/Views/Books/Categories/Search.cshtml");
}
}
Is there some way I could minimize this so I don't have to have so many controllers/actions for static content? It seems like when creating my ListItem data I could set the Controller to a specific controller that handles static content, like I have done, but is there anyway to use one function to calculate what View to return? It seems like I still need separate actions otherwise I won't know what page the user was trying to get to.
The ListItem.Url contains the full URL path from the application root used in creating the route. The location of the View in the project would correspond to the URL location to keep the organization structure parallel.
Any suggestions? Thanks.
Edit: My Route registration looks like so:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("Shared/{*pathInfo}");
routes.MapRoute("Access Denied", "AccessDenied", new { controller = "Shared", action = "AccessDenied", area = "" });
List<ListItem> listItems = EntityServiceFactory.GetService<ListItemService>().GetAllListItmes();
foreach (ListItem item in listItems.Where(item => item.Text != null && item.Url != null && item.Controller != null).OrderBy(x => x.Url))
{
RouteTable.Routes.MapRoute(item.Text + listItems.FindIndex(x => x == item), item.Url.StartsWith("/") ? item.Url.Remove(0, 1) : item.Url, new { controller = item.Controller, action = item.Action ?? "index" });
}
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
You can use a single Action with one parameter (the View name) which will return all the static pages
public class StaticContentController : Controller
{
public ActionResult Page(string viewName)
{
return View(viewName);
}
}
You will also need to create a custom route for serving these views, for example:
routes.MapRoute(
"StaticContent", // Route name
"page/{viewName}", // URL with parameters
new { controller = "StaticContent", action = "Page" } // Parameter defaults
);
I see in your example that you specify different folders for your views. This solution will force you to put all static views in the Views folder of the StaticContentController.
If you must have custom folder structure, then you can change the route to accept / by adding * to the {viewName} like this {*viewname}. Now you can use this route: /page/Books/Categories. In the viewName input parameter you will receive "Books/Categories" which you can then return it as you like: return View(string.Format("~/Views/{0}.cshtml", viewName));
UPDATE (Avoiding the page/ prefix)
The idea is to have a custom constraint to check whether or not a file exists. Every file that exists for a given URL will be treated as static page.
public class StaticPageConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
string viewPath = httpContext.Server.MapPath(string.Format("~/Views/{0}.cshtml", values[parameterName]));
return File.Exists(viewPath);
}
}
Update the route:
routes.MapRoute(
"StaticContent", // Route name
"{*viewName}", // URL with parameters
new { controller = "StaticContent", action = "Page" }, // Parameter defaults
new { viewName = new StaticPageConstraint() } // Custom route constraint
);
Update the action:
public ActionResult Page(string viewName)
{
return View(string.Format("~/Views/{0}.cshtml", viewName));
}