Web API
Introduction
MatchPoint offers a WebApiConfiguration class to provide web service endpoints. The configured endpoints all run under the same base address which points to a standard SharePoint service. Therefore all security aspects and authentication are handled by SharePoint itself.
The basic idea behind the web api configuration is to provide an easy way to configure web api endpoints which can process parameters and optionally return values. When doing this many well-known concepts of MatchPoint (e.g. DataProviders) can be used to make the processing of data easier.
The base URI of the service always looks like this:
<http://myHost/_vti_bin/Colygon.MatchPoint/WebApi.svc>
Note that for the service to work, the following file has to exist:
<15Hive>\ISAPI\Colygon.MatchPoint\WebApi.svc
This file is automatically added when the Colygon.MatchPoint.wsp
is installed.
Basic Control Flow
To get a better overall understanding of what’s happening, let’s imagine a request to the following web api service URL is made:
http://myhost:2341/_vti_bin/Colygon.MatchPoint/WebApi.svc/myPathPrefix/myParametersUrlTemplate/foo
The control flow is as follows:
- Find the
first WebApiConfiguration
which matches the host, port and protocol (
http://myhost:2341
(WebApplication), configured as WebApplicationUrls) and which has the correct PathPrefix (myPathPrefix
)- If no matching configuration could be found a
404 response
is returned.
- If no matching configuration could be found a
- In this configuration find the first endpoint for which
the RouteUrlTemplate
(
myParametersUrlTemplate/foo
) matches- If no matching endpoint could be found a
404 response
is returned.
- If no matching endpoint could be found a
- If the endpoint has
a RequestValidator
configured, validate the request with this validator
- If the request did not validate successfully a
401 response
is returned.
- If the request did not validate successfully a
- All known information is added to an execution scope, so it’s
available via variables:
- All incoming Headers
Expression variable:RequestHeaders
- RouteParameters (extracted from the
RouteUrlTemplate)
Expression variable:RouteParameters
- Payload (extracted from the actual request payload by using the
configured PayloadFormat
and optionally
the PayLoadTargetType).
Expression variable:Payload
Note: This is only available for requests which can contain a payload (e.g. POST).
- All incoming Headers
- If configured, the BeforeActions are executed
- If configured, either
the ActionExpression
or
the DataProvider
is executed (never both!) and the result is temporary kept
- The result is stored in the expression variable
Result
and added to the scope
- The result is stored in the expression variable
- If configured, the result is transformed according to
the ResultPropertyMappings
- The mapped result is stored in the expression variable
MappedResult
and added to the scope
- The mapped result is stored in the expression variable
- If configured,
the AfterActions
are executed
- The result of the ActionExpression and the DataProvider can be
accessed via the expression variables
Result
andMappedResult
- The result of the ActionExpression and the DataProvider can be
accessed via the expression variables
- If the result is of the type IResponseHandler the ProcessResponse method of the result is called, else the result is processed according to the configured ResponseFormat. The processed result / data is then added as the body to the response
- The configured ResponseHeaders are added to the response
- The response is sent back
- If the result is null the entity body of the response will be suppressed.
If errors occur while processing these steps a 500 response
is returned. Details of the errors (and also other trace
messages) are logged and can be seen by using the ULS tool.
Configuration
In the following chapters the most important configuration members are described. Note that some configurations options are omitted because they are either self-explanatory or not that important.
PathPrefix & WebApplicationUrls
An arbitrary number of WebApiConfiguration’s can be created. Each configuration must have a unique PathPrefix configured. The path prefix is added to the base URI of the service:
http://myhost/_vti_bin/Colygon.MatchPoint/WebApi.svc/<myPathPrefix>
If there are multiple configurations which havethe same PathPrefix configured, the first found configuration willbe used.
It's also important to specify to which web
applications the configuration should apply by
specifying WebApplicationUrls.
The selection of the correct web application is important for different
reasons:
The service can only be reached via the selected WebApplicationsUrls.
Another reason are the security aspects. For example, if the web service
should be accessible without any authentication a web application with
anonymous access enabled can be created (or an existing one can also be
extended) and then selected in the configuration.
EndPoints
Next comes the most important part of the configuration: Endpoints. MatchPoint offers four different endpoint types:
- DeleteEndpoint
- GetEndPoint
- PostEndPoint (can have a payload)
- PutEndPoint (can have a payload)
Note that the endpoints which have a payload do have more configuration settings than the one without.
RunAsSystemAccount
By default the code is executed under the user which is authenticated on the web app on which the service runs or under the anonymous user, if anonymous authentication on the web app is enabled. If you check RunAsSystemAccount the code will run under the application pool user.
RouteUrlTemplate
Each endpoint has various configuration options. The most important is probably
the RouteUrlTemplate.
This template "completes" the URL, by adding other parts to the URL path
or by adding query parameters. The requested URL is compared to the
RouteUrlTemplate of each EndPoint. The first end point which matches is
used. If no matching RouteUrlTemplate can be found a 404 response
is returned.
Example template without any parameters:
myRouteUrlTemplate
This would match all this URL:
http://<myhost>/_vti_bin/Colygon.MatchPoint/WebApi.svc/<myPathPrefix>/myRouteUrlTemplate
Example template with parameter:
myParamtersUrlTemplate/{myParam}
This would match all URLs which look like this:
http://<myhost>/_vti_bin/Colygon.MatchPoint/WebApi.svc/<myPathPrefix>/myParamtersUrlTemplate/<parameterValue>
The parameterValue
can vary and be an arbiratry string.
The configured parameters can later (e.g. in the ActionExpression) be accessed via
RouteParameters["<ParamName>"]
or RouteParameters.<ParamName>
.
Naturally it’s also possible to make more
complex RouteUrlTemplates and to have multiple parameters:
complexTemplate/{param1}?param2={param2}
Which would match something like this:
http://<myhost>/_vti_bin/Colygon.MatchPoint/WebApi.svc/<myPathPrefix>/complexTemplate/foo?param2=bar
RouteParameters
It’s possible to
define RouteParameters.
Every route parameter has to have the same name as a parameter which is
specified in the RouteUrlTemplate. When processing all bound variables
and all query parameters which were found within the RouteUrlTemplate,
MatchPoint tries to find a corresponding configured RouteParameter. If
one is found the parameter value will be converted to the
specified ValueType
and then
the ConstraintExpression
is evaluated. If any ConstraintExpression evaluates to false a 404 response
is returned. When all
ConstraintExpressions evaluated to true all parameters are added to the
RouteParameters variable. Variables or parameters for which no
corresponding configured RouteParameter exists are directly added to the
RouteParameters
variable.
The RouteParameters
variable can later be
accessed in the ActionExpression, the DataProviders, the
PropertyMappings and the ResponseHeaders.
ActionExpression & DataProvider
Either
the ActionExpression
or the DataProvider
can be used to process the data and to return a result. In both cases
the variables RouteParameters
, Payload
(only available for endpoints which have a
body) and RequestHeaders
can be used. The type
of the payload depends on the
configured PayloadFormat
and
the PayloadTargetType
(optional). If a PayloadTargetType is specified and the PayloadFormat is
either Json or Xml the payload is automatically deserialized to the
specified target type, else the payload is a Json object or an
XmlDocument.
Note that it’s purely optional for the ActionExpression
to return a
result.
ResultPropertyMappings
It’s possible to configure ResultPropertyMappings which can transform the result, returned by either the ActionExpression or a DataProvider, to a "dictionary-styled" result. If any property mappings are defined the result will be transformed – even if it’s null! The property mapping is especially useful to exclude some properties from the actual result (this may be needed, because not everything is wanted or not even serializable) and also to transform the result to a format which is expected by the client. This is especially useful if Xml or Json is used as the ResponseFormat.
The property mapping can contain normal Mappings
and CollectionPropertyMappings.
A normal Mapping will just store (and optionally transform) the DataItem
in a variable with the configured name
whereas the CollectionPropertyMapping allows to specify a part of the
data which is a collection (Array, list, etc.). For this data it’s then
again possible to define mappings (recursive).
To clarify the usage of the property mappings, let’s assume that our ActionExpression returned the following C# object:
{Name: "Foo", Webs: [SPWeb1, SPWeb2]}
Since this object contains SPWebs we cannot return it directly. Also we’d like to specify directly in the result set how many webs are contained – as is often done by services which return a Json response with multiple items.
This means our configuration will look like this:
<ResultPropertyMappings>
<Mapping type="Mapping">
<Name>Name</Name>
<ValueExpression>
<![CDATA[DataItem.Name]]>
</ValueExpression>
</Mapping>
<Mapping type="Mapping">
<Name>NumberOfWebs</Name>
<ValueExpression>
<![CDATA[DataItem.Webs.Length]]>
</ValueExpression>
</Mapping>
<Mapping type="CollectionPropertyMapping">
<ValueExpression>
<![CDATA[DataItem.Webs]]>
</ValueExpression>
<Mappings>
<PropertyMapping type="PropertyMapping">
<Name>Name</Name>
<ValueExpression>
<![CDATA[DataItem.Name]]>
</ValueExpression>
</PropertyMapping>
<PropertyMapping type="PropertyMapping">
<Name>Url</Name>
<ValueExpression>
<![CDATA[DataItem.Url]]>
</ValueExpression>
</PropertyMapping>
</Mappings>
<Name>Webs</Name>
</Mapping>
</ResultPropertyMappings>
As can be seen in the configuration snippet only the Name and the Url of the SPWebs are extracted – the rest is ignored. This new result can now be serialized without any problem.
If the ResponseFormat is set to Json the following would be returned:
{
Name: "Foo",
NumberOfWebs: 2,
Webs: [
{
Name: "Web 1",
Url: "http://foo.bar"
},
{
Name: "Web 2",
Url: "http://bar.foo"
}
]
}
BeforeActions & AfterActions
It's possible to define
BeforeActions
and
AfterActions.
The only difference between the two is, that the BeforeActions are
executed before the ActionExpression / the DataProvider has been
executed whereas the AfterActions are executed after them. In the
AfterActions the result of the ActionExpression / DataProvider can be
accessed by the expression variables Result
and MappedResult
(to access the result
after the ResultPropertyMappings have been applied).