I am building a RESTful API for a web site that allows users to create widgets and tabs containing widgets (think igoogle.com/ netvibes.com) and I want to share my URL design for your insights.
Here are the simple rules:
There is a static list of widgetTypes available for a user to pick.
A user can create one or more widgetInstances of each widgetType.
A user can create one or more tabs/ dashboards containing widgetInstances
This API needs to only serve JSON that will be consumed by JavaScript. We can also assume that all authentication will be taken care of through cookies.
The API needs to serve:
CRUD of user's Tabs
CRUD of specific user widgetInstances
Retrieval of all tabs for a user
Retrieval of all widgetInstances for a given tab.
Retrieval of all available widgetTypes for a user to add widgets from.
Design:
Tab controller:
widgetAPI.com/tabs -> Returns meta data (id, title) of all tabs available to a user.
widgetAPI.com/tabs/1 -> Returns meta data (title) of tab id 1. If sent with POST, updates tab id 1.
widgetAPI.com/tabs/1/widgets > Returns all widgetInstances of tab id 1.
Question 1: Ideally I'd like to follow the design of widgetAPI.com/tabs/1 also returning all the widgetInstances of the given tab but with that design, widgetAPI.com/tabs may return far too much data as I would have to return all the widgets for all the tabs. Hence I need to create a separate "widgetAPI.com/tabs/1/widgets" URL but that also has to return the tab meta data as I don't want to make two HTTP calls to get meta data & widgets. Please advise as I am not sure of the best approach here.
widgetAPI.com/tabs/create -> Create a new tab
widgetAPI.com/tabs/delete/123 -> Delete tabid 123
Widget Controller:
widgetAPI.com/widgets/123 -> Return data for widgetInstanceId 123. Updates 123 if sent through POST.
widgetAPI.com/widgets/Create?typeID = 2 -> Creates a new widgetInstance of typeid = 2. This will only be a POST request so typeId could be a post parameter.
widgetAPI.com/widgets/delete/123 -> Delete widgetInstance 123
Question 2 So there is one rule I havent been able to fulfill yet. I need to return all the widgetTypes available and I am not sure how to fit this request into the previous two controllers. I am currently leaning towards just serving this separately. So something like widgetAPI.com/getWidgetTypes. Thoughts?
Thanks guys. If you could critique on the overall design, just address the questions or mention anything I should watch out for, that would be great as this is my first time designing a RESTful app. Thanks again.
widgetAPI.com/tabs/1 -> Returns meta data (title) of tab id 1. If sent
with POST, updates tab id 1.
A POST to the above URL should not update tab 1. A PUT to that URL should update tab 1.
widgetAPI.com/tabs/create -> Create a new tab
To create a new tab, you should POST to widgetAPI.com/tabs
widgetAPI.com/tabs/delete/123 -> Delete tabid 123
To delete tab 123, send a DELETE to widgetAPI.com/tabs/123
widgetAPI.com/widgets/123 -> Return data for widgetInstanceId 123. Updates 123 if sent through POST
To update widget 123, send a PUT to widgetAPI.com/widgets/123
widgetAPI.com/widgets/Create?typeID = 2 -> Creates a new widgetInstance of typeid = 2. This will only be a POST request so typeId could be a post parameter.
To create a new widget, send a POST to widgetAPI.com/widgets. The typeId should be part of the POST request body.
widgetAPI.com/widgets/delete/123 -> Delete widgetInstance 123
To delete widget 123, send a DELETE request to widgetAPI.com/widgets/123
Answer 1: I like the widgetAPI.com/tabs/1/widgets URL design. Also, I like the idea of making 2 separate calls for the metadata and the data.
Answer 2: I think you should do this with a separate controller. I don't like the URL though. Instead, I like HTTP GET widgetAPI.com/widget-types or just widgetAPI.com/widgettypes.
As a general rule, unless you want your clients to be able to create URL identifiers, follow this pattern:
URL: /whatever-resource
GET - returns all resources of this type
POST - create a new resource
URL: /whatever-resource/{id}
GET - return single resource with that id
PUT - update resource with that id
DELETE - delete resource with that id
You can also allow PUT requests to /whatever-resource/{id} to create a resource, but the client / user must specify the id (implicitly, the URL, since the URL contains the id). If you don't want users to provide this, but rather have the server generate it, then POST to /whatever-resource to create the resource.
Related
How can i log requests that are going on some link?
I need to store requests Headers, Verb (Get or Post etc.), Request Data and Request Body.
It's must be some separate application like Fiddler.
DESC: I have web application. It makes some search. I want to log data of search request using another application which can log any requests for some site (in my case for my web app). How to make it? I make research for solution but find many examples where user can create some Module or Filter which must be included in web application. This case for me is not allowed.
If you have control of both sides, you can basically do whatever you want..
Maybe link to an action first that acts as a tracker:
public ActionResult Track()
{
//get whatever data you want here
//Request.Headers, Request.RequestType ect
//track the data in a database or whatever
SaveSomeData();
//get the original url from a post variable, or querystring, where you put it
var redirectUrl = Request["redirect"];
return Redirect(redirectUrl);
}
Then you would change your links for example a link to http://google.com, would change to
http://mywebsite.com/mycontroller/track?url=http://google.com
Another possible way would be to create a proxy, and monitor the data that goes through it.
Need a better idea of what you need though to help out more.
I am sure this has been answered somewhere else but I cannot seem to find a definitive posting anywhere.
Most of the postings regarding hierarchical routing talks about when you want an unlimited number of tokens in the Url. My question deals more with when it does not make sense for a given Entity to exists without being associated in the context of another Entity.
For example, I have a Contract entity along with the expected Contract controller. It has the standard Actions like Index, Edit, Create, and so on. My Urls look like
/Contracts/ ' list all contracts
/Contracts/Create/ ' display form to create new contract
/Contracts/Edit/87Y5r3/ ' display form to edit contract 87Y5r3
Now imagine that I Order entity that must be associated with a given Contract. Using the (almost) default routing I would have Urls of
/Orders/ ' display all orders across all contracts
/Orders/Index/87Y5r3 ' display all orders for contract 87Y5r3
/Orders/Create/87Y5r3 ' display form to create new order for contract 87Y5r3
/Orders/Edit/87Y5r3/45 ' display form to edit order 45 under contract 87Y5r3
I can of course leave the almost default routing while tweaking it to support the additional parameters like contract number and order number.
Or I can change my routing to show that Orders comes under Contracts in the hierarchy. As I see it, I have several paths to pursue:
1) Have a single controller that handles both Contracts and Orders along with numerous custom routes for mapping actions to methods. This gives me Urls along the lines of
/Contracts/ ' maps to Index action in the Contract controller
/Contracts/Create/ ' maps to Create action in the Contract controller
/Contracts/Orders/ ' maps to IndexOrders action in the Contract controller
/Contracts/Orders/Index/87Y5r3 ' maps to IndexOrders action in the Contract controller
/Contracts/Orders/Edit/87Y5r3/45 ' maps to EditOrders action in the Contract controller
While I cannot imagine any good argument for having just a single controller, I am guessing that this good also be split into a Contracts controller and an Orders controller with an appropriate route(s).
The main point to take in is that the Contract Number is coming towards the end of the Url.
2) Another option is the separate controllers but with the following Urls. This seems a bit more natural (logical) to me.
/Contracts/ ' maps to Index action in the Contract controller
/Contracts/Create/ ' maps to Create action in the Contract controller
/Contracts/?????/87Y5r3/Orders/Index/ ' maps to Index action in the Order controller
/Contracts/?????/87Y5r3/Orders/Edit/45 ' maps to Edit action in the Order controller
/Contracts/?????/All/Orders/ ' maps to Index action in the Order controller
In this case the contract number comes after the Contract token in the Url with the order number coming towards the end. I have identified a few issues/concerns
How to handle Order data that goes across all Contracts. As you can see handled that with a special "All" token.
What action do I use for the Contracts portion of the Url. By default, routing in Mvc is /{controller}/{action}/{id}.
3) The third option I have seen posted (but do not understand enough to evaluate pros and cons) is to use RESTful API. I believe this would (could ??) resolve my second concern about what action to use for the Contract when working with Orders. The basic idea is that the action is replaced with an HTTP verb like DELETE or PUT which would only need to apply to the entity at the end of the Url.
In this case I would end up with something like
GET /Contracts/ ' maps to Index action in the Contract controller
POST /Contracts/Create/ ' maps to Create action in the Contract controller
GET /Contracts/87Y5r3/Orders/ ' maps to Index action in the Order controller
PUT /Contracts/87Y5r3/Orders/45 ' maps to Edit action in the Order controller
GET /Contracts/All/Orders/ ' maps to Index action in the Order controller
While RESTful may be the way to go I definitely do not know enough about it and my initial reaction is that it adds complexity and limitations (how many verbs are there???) that may limit its usefulness.
Based on my quick reading, going with a RESTful approach (including ASP.NET Web API as suggested by #Robotsushi below) does not really answer my question. RESTful seems to be something to be considered if my pages were requesting data via AJAX and JSON. In that sense (requesting data only) it provides an approach to Urls. However, my question is focused more on the standard MVC model where the action passes a Model to a View. At the end of the day I still need to present web pages to my users...
Did I sum this up clearly? Any other strategies that I am missing? This has to be a fairly common scenario so I am surprised I have not found a ton of articles.
I simplified the examples a bit but in my case I actually need to take this to a third level --- Contracts / Orders / Projects.
Thanks
This is purely my opinion.
I would recommend that you use a web service. If you can't you can still use HTTP POST. It will be easier to send a complex data structure without cluttering the URL with alot of unstructured key/value pairs.
If you used this strategy you would be able to send XML or JSON as your data structure, and get the complex entity representation you probably need.
If you are unfamiliar with HTTP based web services, then check out ASP.NET Web API.
Good Luck
EDIT
You can HTTP POST data to your MVC controller. If you do this then you can use a complex serialization format such as XML or JSON to send up your data. This will allow for the hierarchical nesting that your entities require.
I should have been more clear about web services. The type of operations you are performing seem like they might be better off in a web service. However regardless of whether or not you choose to use a web service your mvc controller can accept data can be correctly represented using HTTP POST actions.
I hope this helps.
I have three models and their corresponding tables and controllers:
Request
DirectPatch
UTPFiberPatch
The user creates a new request and defines the type of request: Direct or UTP/Fiber
Once the user hits save, the Request will be saved and the user will be redirected to an edit screen to create all the patch entries corresponding to that request.
Each patch entry will be saved as a single row in either the Direct or UTP table depending on the type of request selected. A column called request_id will act as the foreign key.
The view and form will be different for both Direct and UTP/Fiber.
The user can view all requests on a single homepage and click to edit. The user can then see all the patch rows for that request on a single page and click to edit existing or add new.
What would be the best way to set up the routing, controllers and
views for this?
How does the Request ID get passed when creating new patches and automatically saved?
I'm not sure exactly what the technical name is for the problem I'm having if there is one, but I'm happy to add more detail and answer questions if needed.
You could use a polymorphic association on the request model:
belongs_to :patch, :polymorphic => true
Add to your Request table two columns:
patch_id : integer
patch_type : string
When you create a request and the user selects the patch type, you assign this patch object to your request, which will populate the two new columns. From you request model you can then call
request.patch
Which will give you back the appropriate type of patch based on the details stored in the database (note - you can't do eager loading with polymorphic associations).
To do the views you can render a partial in the edit screen, based upon which type of patch it is.
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.
Our web application needs one common parameter in every action method.
In our case it is the customer account id and we need to support the following scenarios
a. A group of users might have the same account id which can be derived from the user profile.
b. Customer Support team should be able explicitly supply the account id of a customer and also should be able to switch the account on any page
We are trying to not to use asp.net session to store this kind of data.
Are there any other options to store and manage this kind of common parameter data?
Write it out as an ecrypted value to hidden field on your master page and supply the value to every view. When the user is in a a customer role, place a change account "control" on the page that is able to retrieve and update the account data -- via AJAX, perhaps -- to change the current account id. It might be easiest to do this with a custom base controller which gets the data via the ValueProvider directly and stores it as a property on the page rather than having it be a parameter to every method.
Use Routing for the value. So if you need to change the id you can use another URL or post it as a parameter.
Whenever you need the value just ask the ValueProvider for it.
In case it is blank - use the one from user profile.
Of course you'd better write small method that will do just that:
// Register route like:
route.MapRoute("ProvidesAccountId", "{controller}/{id}/account{accountId}/{action}.aspx")
// Property on the base controller
protected Account CurrentAccount {
get {
var accountId = ValueProvider.GetValue<int?>("accountId"); // GetValue is just a helper
if (accountId.HasValue)
return YourRepositor.GetAccountBy(accountId.Value);
return CurrentUser.Account;
}
}
Not to use current user's account hit the URL: Profile/123/account/Edit.aspx
To use another account you can hit the URL: Profile/123/account-456/Edit.aspx
You get the idea.
Cheers,
Dmitriy.