Controllers and routing
A DelphiMVCFramework application has always the fllowing architecture:
- One Application
- Many Controllers
- A Controller is a class inherited from TMVCController
- Many actions for each controller
- Controller actions are its methods instrumented with specific RTTI attributes
Then each controller is addressed using a piece of URL and each action is selected using the second part of the URL.
Example: www.myserver.com/blog/posts/2016
- Server name:
www.myserver.com
- Controller:
blog
(will be mapped to a controller to instantiate) - Action:
posts
(will be mapped to a method to call) - Action' URL parameter: 2016 (parameter to pass to the action, it is optional and defined by the action itself)
Understanding routing is fundamental part of any DMVCFramework development. If you dont know how the router works, you cannot write proper RESTful service.
The router is based upon 4 RTTI attributes. The most important one, and the only always required is MVCPath
.
MVCPath attribute
The MVCPath
attribute is the most important because it defines the first routing to which the controller should respond. It is also the only attribute required.
The MVCPath
attribute is applicable to the controller and to the action itself. Its syntax is quite simple: MVCPath('/path')
.
Let's see the following example.
type
[MVCPath(‘/blog’)] //at controller level
TBlog = class(TMVCController)
public
[MVCPath(‘/posts/($year)/($month)/($title)’)] //at action level
procedure GetArticle(CTX: TWebContext);
end;
The controller defines the resource name blog
while the action defines the nested resource posts
.
Please, note the URL parameters defined in the MVCPath
attribute at the action level.
The syntax to define URL parameter is: ($paramname)
Here are some URI which matches with the previous controller/action:
/blog/posts/2011/05/a-brand-new-framework
/blog/posts/2013/05/rest-rest-for-delphi
/blog/posts/1/5/thisAndThat
/blog/posts/2013/05/123
The following URI don't matches with the previous controller/action:
/blog/posts/2011
(lacks some required parameters, dmvcframwork will return404: Not Found
)/posts/2013/05/rest-rest-for-delphi
(there isn't a controller namedposts
, dmvcframwork will return404: Not Found
)
If we'd like to define a resource which returns the posts
by year, we could add another nested resource as follows:
type
[MVCPath(‘/blog’)] //at controller level
TBlog = class(TMVCController)
public
[MVCPath(‘/posts/($year)’)] //at action level
procedure GetPostsByYear(CTX: TWebContext);
[MVCPath(‘/posts/($year)/($month)/($title)’)] //at action level
procedure GetArticle(CTX: TWebContext);
end;
Now, the service will response also to URI like the following:
/blog/posts/2011/05/a-brand-new-framework
(specific article)/blog/posts/2013
(all the posts written in the 2013)/blog/posts/2016
(all the posts written in the 2016)
Please, note that action parameter of by TWebContext
is optional. In case you dont use it you can access to the same object defined at controller level named Context
. Considering this, the previous actions can be rewritten as follows:
type
[MVCPath(‘/blog’)] //at controller level
TBlog = class(TMVCController)
public
[MVCPath(‘/posts/($year)’)] //at action level
procedure GetPostsByYear;
[MVCPath(‘/posts/($year)/($month)/($title)’)] //at action level
procedure GetArticle;
end;
And the parameters can be accessed as following into the actions:
procedure TBlog.GetPostsByYear;
var
lYear: String;
begin
//Context is a controller property
lYear := Context.Request.Params['year'];
//do something with year
end;
procedure TBlog.GetArticle;
var
lYear, lMonth, lTitle: String;
begin
//Context is a controller property
lYear := Context.Request.Params['year'];
lMonth := Context.Request.Params['month'];
lTitle := Context.Request.Params['title'];
//do something with the variables
end;
Strongly Typed Action
DelphiMVCFramework router can automatically inject values into actions reading them from the MVCPath attribute. Using strongly typed actions, the sample controller become as follows:
type
[MVCPath(‘/blog’)] //at controller level
TBlog = class(TMVCController)
public
[MVCPath(‘/posts/($year)’)]
//the parameter must have the same name as defined in the MVCPath
procedure GetPostsByYear(year: String);
[MVCPath(‘/posts/($year)/($month)/($title)’)]
procedure GetArticle(year: String; month: Integer; title: String);
end;
And the parameters are accessed as following into the actions:
procedure TBlog.GetPostsByYear(year: String);
begin
//do something with year
end;
procedure TBlog.GetArticle(year: String; month: Integer; title: String);
begin
//do something with the parameters
end;
Strongly typed actions supports the following data types:
- Integer (es. /controller/myaction/1234)
- Int64 (es. /controller/myaction/1234)
- Single (es. /controller/myaction/1234.52)
- Double (es. /controller/myaction/1234.5467)
- Extended (es. /controller/myaction/1234.5467)
- Boolean (es. /controller/myaction/true or /controller/myaction/true)
- TDate (es. /controller/myaction/2016-12-20)
- TTime (es. /controller/myaction/14:12:59)
- TDateTime (es. /controller/myaction/2016-12-20 14:12:59)
- String (es. /controller/myaction/mystring)
In the unit tests there are all the possibile cases explained.
MVCHTTPMethod attribute
TODO
MVCProduces attribute
TODO
MVCConsumes attribute
TODO