I am creating my first ASP.NET web API. I am trying to follow the standard REST URLs. My API would return the search result records. My URL should be –
../api/categories/{categoryId}/subcategories/{subCategoryId}/records?SearchCriteria
I am planning to use oData for searching and Basic / Digest Authentication over IIS. My problem is in the nested resources. Before I return the search results, I need to check whether the user has access to this category and sub category.
Now I created my Visual Studio 2012 – MVC4 / Web API project to start with. In the App_Start folder, there are 2 files that I believe are URL and order of resource related.
1.RouteConfig.cs
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
2.WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
With this model, it works fine if my URL is ../api/records?SearchCriteria but it is not my URL design mentioned above. I understand that I have to do little more reading but so far not able to find the correct article. Need your advice on how to achieve my URL and what changes are needed in these 2 files. Alternatively, are there some other configuration that I am missing here? Thanks in advance.
Asp.net Web API 2 provides Attribute routing out of the box. You can define Route on individual action method or at global level.
E.g:
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
You can also set a common prefix for an entire controller by using the [RoutePrefix] attribute:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/books
[Route("")]
public IEnumerable<Book> Get() { ... }
// GET api/books/5
[Route("{id:int}")]
public Book Get(int id) { ... }
}
You can visit this link for more information on Attribute routing in Web API 2.
Assuming you have a controller named categories, Your WebApiConfig.cs could have a route like this to match your desired url (I would personally leave the /records portion off):
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{categoryId}/subcategories/{subCategoryId}",
defaults: new { controller = "categories", categoryId = somedefaultcategory,
subCategoryId = RouteParameter.Optional }
);
and a method could look like this:
// search a single subcategory
public IQueryable<SearchRecord> Get(int categoryId, int subCategoryId = 0, string SearchCriteria = "")
{
// test subCategoryId for non-default value to return records for a single
// subcategory; otherwise, return records for all subcategories
if (subCategoryId != default(int))
{
}
}
But, what if you want to also return just the categories and not subcategories? You'd need an additional route after the first one that is more generic:
config.Routes.MapHttpRoute(
name: "Categories",
routeTemplate: "api/{controller}/{categoryId}",
defaults: new { controller = "categories", categoryId = RouteParameter.Optional }
);
with two methods like:
// search a single category
public IQueryable<SearchRecord> Get(int categoryId, string SearchCriteria = "")
{
}
// search all categories
public IQueryable<SearchRecord> Get(string SearchCriteria = "")
{
}
Related
This question already has an answer here:
Multiple levels in MVC custom routing
(1 answer)
Closed 5 years ago.
So Im working on an MVC 4 application. My problem is for a certain controller the Url can be http://domain/category/clothing/1256 or it can be http://domain/category/clothing/shirts/formal/128'. As you can see the depth of the url changes but all must route to the category controller.
Since the max dept of the url is not known, I cant User Routeconfig as I dont know when the parameters will come.
routes.MapRoute(
name: "Category",
url: "Category/{ignore}/{id}/{SubCatId}/{SubSubCatId}",
defaults: new { controller = "Category", action = "Index", CatId = UrlParameter.Optional, SubCatId = UrlParameter.Optional, SubSubCatId = UrlParameter.Optional },
namespaces: new[] { "CSPL.B2C.Web.Controllers" },
constraints: new { id = #"\d+" }
);
The above code will not work as it accounts for only one level. Any ideas on how to achieve this
The Index method of category controller
public ActionResult Index(string id, string SubCatId, string SubSubCatId)
{
return view();
}
Your only option is a catch-all param. However, there's two caveats with that:
The catch-all param must be the last part of the route
The catch-all param will swallow everything, including /, so you'll have to manually parse out individual bits you need with a regular expression or something.
Essentially, you'll just change the route to:
routes.MapRoute(
name: "Category",
url: "Category/{*path}",
defaults: new { controller = "Category", action = "Index" },
namespaces: new[] { "CSPL.B2C.Web.Controllers" }
);
Then, your action would need to be changed to accept this new param:
public ActionResult Index(string path)
{
// parse path to get the data you need:
return view();
}
I inherited some ASP.Net MVC code and am tasked with adding some new features. I am a complete beginner using ASP.Net MVC and come from a background of mainly using Web Forms.
I added a new controller (ApiController) and I added the following actions to it:
// GET: /Api/Index
public string Index()
{
return "API Methods";
}
// GET: /Api/DetectionActivity
public JsonResult DetectionActivity()
{
var detections = from d in db.Detections
orderby DbFunctions.TruncateTime(d.CreationTime)
group d by DbFunctions.TruncateTime(d.CreationTime) into g
select new { date = g.Key, count = g.Count() };
ViewBag.DetectionCounts = detections.ToList();
return Json(detections, JsonRequestBehavior.AllowGet);
}
My RouteConfig.cs has the following registered routes.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Dashboard", action = "Index", id = UrlParameter.Optional }
);
This looks like the tutorials I've been reading but it's not working and I'm probably missing something.
If I go to localhost:21574/api I see the output from the Index() action, "API Methods".
If I go to localhost:21574/api/DetectionActivity it throws a 404 with the following data in the response:
{
"Message":"No HTTP resource was found that matches the request URI 'http://localhost:21574/Api/DetectionActivity'.",
"MessageDetail":"No type was found that matches the controller named 'DetectionActivity'."
}
I'm thinking there is something I need to do that I'm not.
Any suggestions on what to do next?
Update 1
I tried this with my RouteConfig.cs
routes.MapRoute(name: "ApiController",
url: "{controller}/{action}"
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Dashboard", action = "Index", id = UrlParameter.Optional }
);
These were my results:
If I go to localhost:21574/api I see the output from the Index() action, "API Methods". Same as before.
If I go to localhost:21574/api/DetectionActivity it throws a 404 with the following data in the response:
{
"Message":"No HTTP resource was found that matches the request URI 'http://localhost:21574/Api/DetectionActivity'.",
"MessageDetail":"No type was found that matches the controller named 'DetectionActivity'."
}
Same as before.
If I go to localhost:21574/Api/Api/DetectionActivity it throws a 404 with this data in the response:
{
"Message":"No HTTP resource was found that matches the request URI 'http://localhost:21574/Api/Api/DetectionActivity'.",
"MessageDetail":"No type was found that matches the controller named 'Api'."
}
Now it's saying it can't find a controller named "Api".
from your Route Config
the URL should be: localhost:21574/Api/Dashboard/DetectionActivity
or if you really need localhost:21574/Api/DetectionActivity (not recommended)
change your Register method in WebApiConfig class to
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{action}/{id}",
defaults: new { controller = "Dashboard", action = "Index", id = RouteParameter.Optional }
);
In a ASP.NET Web api project I have a VacationController where I want to use these action methods.
How can I construct the routes to achieve this?
public Enumerable<Vacation> GetVacation()
{
// Get all vactions
return vacations;
}
public Vacation GetVacation(int id)
{
// Get one vaction
return vacation;
}
public Enumerable<Vacation> ByThemeID(int themeID)
{
// Get all vactions by ThemeID
return vacations;
}
I would like the URL's to look like this
/api/vacation // All vacations
/api/vacation/5 // One vacation
/api/vacation/ByThemeID/5 // All vacations from one theme
Edit 30-10-2013
I have tried Pasit R routes but I can't getting to work. I verily tried every combination I could think of.
This is what I have know. As you can see I have added a extra parameter at the bein of the route. I realized that I needed that in order to seperate the Vacations sold onder different labels.
Here are the routes I use. and the work OK for these URL's
/api/vacation // All vacations
/api/vacation/5 // One vacation
/api/vacation/ByThemeID/5 // All vacations from one theme
But it dosn't work for the last URL
config.Routes.MapHttpRoute(
name: "DefaultApiSimbo",
routeTemplate: "api/{label}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And here my Action method in the VacationController
// ByThemeID api/{label}/Vacation/ByThemeId/{id}
[HttpGet]
public IEnumerable<Vacation> ByThemeID(string label, int id)
{
return this.repository.Get(label);
}
// GET api/{label}/Vacation
public IEnumerable<Vacation> GetVacation(string label)
{
return repository.Get(label);
}
// GET api/{label}/Vacation/{id}
public Vacation GetVacation(string label, int id)
{
Vacation vacation;
if (!repository.TryGet(label, id, out vacation))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
return vacation;
}
Can some one give my a push in the right direction ;-)
Thanks in advance
Anders Pedersen
Assuming that the class is name VacationController, then a default routes for these methods would look something like:
/api/Vacation/GetVacation
/api/Vacation/GetVacation?id=1
/api/Vacation/ByThemeID?id=1
This is all assuming that the routing has note been updated.
add defaults action = "GetVacation" and make id as optional
the ApiController base class could handle overload GetVacation() and GetVacation(int id) selection automatically.
to register WebApiConfig
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{*param}",
defaults: new { action = "Get", param = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Vacation",
routeTemplate: "api/vacation/{action}/{*id}",
defaults: new { controller = "Vacation", action = "GetVacation", id = RouteParameter.Optional }
);
}
I've tried to step through this extensively and could not find the answer. There are about 30 methods defined that all map correctly but this single method does not. It differs because it has 3 params while the others do not.
[HttpGet]
public Info<List<SEOJobTitleLocation>> GetSeoTopLocations(string jobTitle, string city = "", string state = "")
{
return _jobs.GetSeoTopLocations(jobTitle, city, state);
}
and the Areas code is as follows:
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.MapHttpRoute(
name: "AreaApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { area = AreaName, id = RouteParameter.Optional },
constraints: new { id = #"^\d+$" }
);
context.Routes.MapHttpRoute(
name: "AreaApiWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { area = AreaName, action = "get", id = RouteParameter.Optional }
);
}
I've gone as far as downloading the symbols from Microsoft to step through the build and could see this particular method is not being generated in the code. I have absolutely no clue as to why. Sorry I cannot give more info than that.
The specific error message I get is as follows:
No HTTP resource was found that matches the request URI...
Thank you for looking into this in advance.
Try removing the two optional parameters and "defaulting" those inside the function, just as a test to see if it will instantiate the function.
This is my first post after being a long-time lurker - so please be gentle :-)
I have a website similar to twitter, in that people can sign up and choose a 'friendly url', so on my site they would have something like:
mydomain.com/benjones
I also have root level static pages such as:
mydomain.com/about
and of course my homepage:
mydomain.com/
I'm new to ASP.NET MVC 2 (in fact I just started today) and I've set up the following routes to try and achieve the above.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("content/{*pathInfo}");
routes.IgnoreRoute("images/{*pathInfo}");
routes.MapRoute("About", "about",
new { controller = "Common", action = "About" }
);
// User profile sits at root level so check for this before displaying the homepage
routes.MapRoute("UserProfile", "{url}",
new { controller = "User", action = "Profile", url = "" }
);
routes.MapRoute("Home", "",
new { controller = "Home", action = "Index", id = "" }
);
}
For the most part this works fine, however, my homepage is not being triggered! Essentially, when you browser to mydomain.com, it seems to trigger the User Profile route with an empty {url} parameter and so the homepage is never reached! Any ideas on how I can show the homepage?
Know this question was asked a while back but I was just looking to do the same sort of thing and couldn't find any answer that quite solved it for me so I figured I'd add my 2 cents for others that may also look to do the same thing in future.
The problem with the proposed solution above (as mentioned in Astrofaes' comment) is that you would need to create static routes for every controller in your assembly. So in the end I ended up using a custom route constraint to check whether or not a controller exists in the executing assembly that could handle the request. If there is then return a false on the match so that the request will be handled by another route.
public class NotControllerConstraint : IRouteConstraint
{
private static readonly IEnumerable<Type> Controllers = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.BaseType == typeof(Controller));
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return Controllers.Where(c => c.Name == values["id"] + "Controller").Count() == 0;
}
}
Routes can then be set up as follows:
routes.MapRoute("User", "{id}", new { controller = "User", action = "Index" }, new { notController = new NotControllerConstraint() });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Can you not swap the bottom two routes?
The reason that swapping the routes works, is because the {url} route doesn't have a constraint on it against empty strings (which is what your last route is). As a result, it will match the empty string first as it's higher in the route table.
With that in mind, you can either add constraints or add your specifically named routes higher in the routes table, or use the default catch all routes that mvc gives you to start with.
If you want to know which routes are matching at any given moment, then you can use the Route Debugger from Phil Haack.
I was looking to implement same style of url for my MVC 1.0 application.
I have implemented that with information from this post and blog post by Guy Burstein.
Thanks for sharing :)
I have a simlar setup as below:
routes.MapRoute(
"Common",
"common/{action}/{id}",
new { controller = "common", action = "Index", id = "" }
);
routes.MapRoute(
"Home",
"",
new { controller = "Home", action = "Index", id = "" }
);
routes.MapRoute(
"Dynamic",
"{id}",
new { controller = "dynamic", action = "Index", id = "" }
);
This allows me to be flexible and have the routes
mysite.com/
mysite.com/common/contact/
mysite.com/common/about/
mysite.com/common/{wildcard}/
mysite.com/{anything}