Most examples of Web API that I've seen show URIs in this format:
/api/values/ (returns all records)
/api/value/{id} (returns a specific record)
I can think of times I would not like this URI convention, but for my current project it's perfect. Though, it's not working for me and I don't know what I could have done to effect this. For me, I can have one or the other - singular or plural.
If my controller class is named ValuesController, then my Web API is
/api/values/
/api/values/{id}
If my controller class is named ValueController, then my Web API is
/api/value/
/api/value/{id}
I used ADO.NET Entity Data Model to create several entities in model designer. All have a singular naming convention. For example, Activity, State, Country, User, etc. Though, EF appears to create plural database tables no matter.
I created APIControllers for each of these entities. I've tried 2 naming conventions with my controllers - both plural and singular, but I am not seeing the expected (and desired) results with the URIs. The plural and singular Web API are always the same no matter what I try.
FYI, I just checked the default API that is created automatically (ValuesController). To return all records I have to specify /api/values/ and to return a single record I have to specify /api/values/{id} - both are plural and I'm sure it wasn't that way by default.
What effects this? Where is the logic that configures/recognizes a distinguished form of the URI for plural and singular?
The default route that is configured in Web API's RouteConfig.cs file specifies a parameter named controller, similar to this:
routes.MapHttpRoute("Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional };
When you're making a call to either /api/values or /api/values/{id}, the route value for your controller will be values in both cases. Consequently, the controller that Web API resolves by convention will be the ValuesController.
(Note that there's no automatic pluralization performed whatsoever. You can implement a custom IHttpControllerSelector, if you really wanted to implement that behavior. I strongly recommend not to do that, though.)
Related
I'm looking to heavily customize the way my application handles Routes. Essentially, I want to achieve a process something like this:
User requests URL: /custom-url-part-1/custom-url-part-2/gallery/1/date
Application looks for a match in a database table of Routes
Application returns matching Route record, consisting of:
RouteUrl
Controller
Action
Parameters
Defaults
Application looks at RouteUrl and parses any optional parameters {pagenum}, {orderby}
Application checks if these have been supplied in the requested Url
If they have not been supplied, it checks the "Defaults" of the Route record to get them
These are then passed in the RouteData defaults
Parameters are passed in the RouteData defaults
Route data is returned
The core reason for this is I want to have a dynamic table of Routes, that is modified by a CMS in order to store certain data against routes.
The questions I have are:
How can I replicate the way that MVC matches a Uri to a Route? I need to essentially look through the database table and bring back the appropriate Route based on the Uri. Very similar to how the standard ASP.NET MVC functionality looks through the RouteCollections and uses the correct route.
I realise I'm essentially rewriting some functionality that MVC supplies built-in, and replacing the RoutesCollection with a database... but I need to do that in order to achieve two things:
The routes being entirely dynamic, fluid and controlled by the CMS.
Data being stored against those routes which can't be inferred from the Uri itself (so for example, I might want to send a PageId through when a user hits a particular route, but I don't want that PageId in the Uri).
I have been looking at routing in the Web.Api and looking at various ways of representing endpoints. I came across Instagrams REST endpoints. (which has some lovely documentation) Using the Web.Api what would be the best way to set up the routing and controllers for a sitution like Instagrams user endpoints?
User Endpoints
GET/users/user-id Get basic information about a user.
GET/users/self/feed See the authenticated user's feed.
GET/users/user-id/media/recent Get the most recent media published by a user.
GET/users/self/media/liked See the authenticated user's list of liked media.
GET/users/search Search for a user by name.
If I wanted to replicate these endpoints in my app, how would I go about it. Would I just need one controller 'Users' with 5 methods, what kind of routing would I need to direct the REST calls to those methods?
I would structure this in a different way.
GET/user
GET/user/user-id
GET/user?q=user-name
GET/media
GET/media/user-id
GET/media/user-id?selection=recent
GET/media/user-id?selection=liked
GET/feed
GET/feed/user-id
This way you keep your Controllers for a specific target, much like keeping your classes for a single responsibility.
User
Media
Feed
When you use this approach it's much easier for a user to 'guess' the path. And I think you could already guess what each path does without any explanation. For me that's the most important when I'm designing a API.
GET/controller - always returns a item list
GET/controller/id - always returns a specific item
GET/controller?q= - always queries the controller
GET/controller?selection= - always selects a subset from the list off items
Ofcourse this is open for interpretation but it gives you an idea about how I would solve this particular problem and maybe some ideas to think about. Also have a look at this great book from Apigee - Web Api Designs
http://info.apigee.com/Portals/62317/docs/web%20api.pdf
Edit:
To make the routes you named I think you've got 2 (not very ideal) options.
Map a specific route for each url
Make a wildcard route
Option 1
I have not tried, or used this myself but you can find more info here:
Single controller with multiple GET methods in ASP.NET Web API
Option 2
If you go the wildcard route all requests with additional parameters will be routed to your default Get() method. In your get you have to look at the parameters using ControllerContext.Request.RequestUri.AbsolutePath or something like it and choose your actions on it.
config.Routes.MapHttpRoute(
name: "MyApi",
routeTemplate: "api/{controller}/{id}/{*wildcard}",
defaults: new { controller = "index", id = RouteParameter.Optional }
);
I'm looking at developing an application that will include a CMS. I'm a seasoned web forms developer but only really just moving into MVC.
I have a couple of questions that I hope some of you guys can answer:
First, my current web forms CMS allows users to create a page, and then "drop" any number of user controls onto that page they have created. The way I do this is to create an entry in the DB together with the path and then use the LoadControl method.
I can see I can do this with partial views, but partial views have no code behind. If I've potentially got 100 controls that people can drop onto a page, does this mean that the ViewBag in the controller needs to cater for all 100 controls just in case they are used on the view? For example, a web forms user control will contain logic: rptItems.DataSource = blah; rptItems.DataBind()
With MVC, I'm assuming that logic will be in the view controller and the view would access it by the ViewBag? I'm a little confused at how to do this.
Secondly, how would you handle deep routing?
EG:
Store/Products/Category is fine, but what about Store/Products/Category/Delivery/UK ? Would I need to set up a route in global.asax for each route I need? In web forms, I just called the ReWritePath method and handled the routing myself using regular expressions.
Thanks for the time to read this, and hopefully answer some of my queries
For your second question, (ie, "deep routing"), you can handle this within your controller instead of adding real routes. Each part of the url is available via the RouteData.Values collection inside of your controller action. So, your route may look like
~/Store/Products/Category/{*params}
Assuming typical route configuration, this would call the Category(...) action method on ~/areas/store/controllers/storeController, which could then grap delivery and uk from the RouteData.Values collection.
There are a lot of other approaches to this - storing routes in a database and using associated metadata to find the correct controller and method - but I think this is the simplest. Also, it may be obvious, but if you really only need two parameters beyond 'Category' in your example, you could just use
public ActionResult Category(string category, string region)
{
...
}
and a route:
~/store/{controller}/{action}/{category}/{region}/{*params}
Delivery and UK would be mapped to the the category and region parameters, respectively. Anything beyond uk would still be available via the RouteData.Values collection. This assumes that you don't have more specific routes, like
~/store/{controller}/{action}/{category}/{region}/{foo}/{bar}/{long_url}/{etc}
that would be a better match. ({*params} might conflict with the second route; you'll have to investigate to see if it's a problem.)
For your first question:
You can dynamically generate the view source and return it as a string from the controller, eliminating the need to pass a lot of stuff via ViewBag. If a virtual page from your CMS database requires inclusion of partial views, you would add the references to those components when generating the page. (This may or may not address your problem - if not, please provide more information.)
As I am learning MVC 3 it has become apparent that naming is critical to getting the Dbase-Model-Controller-Views to talk together.
Is there an already existing list of naming conventions that should be used in a MVC application?
Something like:
Table names (plural)
Model names (singular of Table Name)
View folder must be same name as controller class (ie Contract -> derived from ContractController)
The only major convention is that controller class names must end with "Controller". Any other conventions simply represent best practices and are not required for your application to work.
The table and model naming conventions you mentioned are there because they make the code "read" better (Select * From products where category = 1; Products.Insert(new Product()))
MVC automatically looks for a view that matches the name of the action method and it starts looking in a folder that has the same name as the controller. However you can easily trump that by specifying the view name in your view result (return View("MyCustomName"))
Not many name conventions exist that will break your application, other than the few such having your controllers end with "Controller" and name inference of the "View()" call from the action name.
MVC is based on the ideology of convention over configuration, so as I found out slowly, there are many conventions that are useful to follow, I have yet to come across a list though. They are subtle and very convenient... usually.
My domain model is this: we have a bunch of schools as the root of the "hierarchy". Each school has teachers and courses, and each course has one teacher. I am trying to model this with the logic of the mvc framework and I 'm quite confused. For example, the \school\details\x should give the first page of a school. That should contain a link to a list of its teachers, and a list to each courses.
A list of teachers means that the index action should be parametric to the school the user is looking at: \teacher\id where id is the school. The same with the course list. And then create teacher or course should also be parametric to what school we are looking at:\teacher\create\x where x=school.
How do I carry around the school id? Is there some neat way to do it, or do I need to pass it around all the time, into every view that needs it? It also makes the site URLs very cryptic. I was thinking of a way to make the url structure like {school-alias}\{controller}\{action}\{id}, still I have to find a way to pass around the school. If this is accomplished, then I need to implement some kind of filter that will not allow a user to perform certain actions if the schoolId he is requesting does not match the one in his profile.
I figure that if I 'm carrying the schoolid around the URL, the site is more REST-like, compared to, for example, getting the schoolId from the user's profile.
I would create acronym for every school. For example:
School no. 1 - ABC
School no. 2 - DEF
If i wanted to list teachers, I would write
http://site-address/ABC/teachers/list or just http://site-address/ABC/teachers
To show basic information about school
http://site-address/ABC
The code for routing would be:
routes.MapRoute(
"Default", // Route name
"{acronym}/{controller}/{action}/{id}", // URL with parameters
new {controller = "School", action = "Details", id = ""} // Parameter defaults
);
I would create authorization action filter on teachers,school and classes controller to check if user has access to school defined by acronym parameter in URL. You can check it by comparing filterContext.RouteData.Values["acronym"] with data stored in profile.
Write an extension method to overload rendering of links that extracts the school identifier ( acronym or whatever you choose to use ) from the routing data and adds it to the route values already passed in. This way your action can choose to use the identifier if it is present but is not required to add it to the view data and you do not have to remember to include it in any action links ( you just have to remember to use your action link overload ).
I would make the action link overload quite obviously different so anyone following behind you can see you are doing something unusual. This could be as simple as Html.SchoolActionLink( ...).
For example:
If your url is http://mydomain.com/abc/teachers/list and your route is defined as {school}/{controller}/{action} then the route value dictionary will have the value "abc" at the key "school". The route values can be accessed via HtmlHelper.ViewContext.RouteData.Values.
In the end I 'm answering my own question.
The real solution to this is :Restfull Routing. It implements the functionality in RoR, which is exactly what I need. Too bad this is not a requirement from more people so that it can go into mvc-trunk.