-
Notifications
You must be signed in to change notification settings - Fork 8
Parameter attributes
It is possible to modify the way method parameters impacts the final HttpRequestMessage. Before we look at how you can create your own attributes, let's look how some pre-packed attributes work.
The alias attribute can be used to change how a url parameter is named.
Let's say you need to call an url which looks like this
/api/customer?order-by=name
It happens that the dash character is not an allowed character in parameter names in C#, so the following code would not compile.
[BaseLocation("api/customer")]
public interface ICustomerApi
{
[Get("")]
Task<HttpResponseMessage> GetCustomer(string order-by);
}
This is where the Alias Attribute comes in handy.
[BaseLocation("api/customer")]
public interface ICustomerApi
{
[Get("")]
Task<HttpResponseMessage> GetCustomer([Alias("order-by")]string orderBy);
}
You can add headers to your HttpRequestMessage by using the HeaderAttribute
. The name of the parameter will be used as name for the header. (This is not only for the Authorization
header. You can use this for any kind of header.)
[Get("resource")]
Task<HttpResponseMessage> Get([Header] string authorization);
If it happens that you want it safe for refactoring, add a header name to the attribute
[Get("resource")]
Task<HttpResponseMessage> Get([Header("Authorization")] string auth);
If the Header is constant, you can do like this:
[Get("resource")]
[AddHeader("Authorization", "mysecret")]
Task<HttpResponseMessage> Get();
The AuthorizationAttribute
and AddAuthorizationAttribute
is a special case of the HeaderAttribute
. You can use them like this.
[Get("resource")]
Task<HttpResponseMessage> Get([Authorization] string auth);
or
[Get("resource")]
[AddAuthorization("mysecret")]
Task<HttpResponseMessage> Get();
The pre-packed ContentAttribute
is used to denote which parameter should be used as body content, and how it should be serialized. By default it is serialized as JSON using a JsonSerializer from Newtonsoft, but you can also configure it to serialize to FormUrlEncoded by aslo applying the attribute passing FormUrlEncoded
to your parameter.
Some of the attributes above are extending ParameterTransformerAttribute
and the others are used by ParameterListTransformers (more on them later). You can create your own ParameterTransformerAttribute
-implementation if you'd like. Override the Apply
method and make the changes you desire to the passed Parameter
object. Here are some examples
public class PrefixAttribute : ParameterTransformerAttribute
{
private readonly string _prefix;
public PrefixAttribute(string prefix)
{
_prefix = prefix;
}
public override void Apply(Parameter parameter, RequestTransformContext requestTransformContext)
{
parameter.Name = _prefix + parameter.Name;
}
}
This attribute would be prefix the name of the parameter with whatever prefix you pass.
[BaseLocation("api/customer")]
public interface ICustomerApi
{
[Get("")]
Task<HttpResponseMessage> GetCustomers([Prefix("p_")]string filter = null);
}
await api.GetCustomers("new");
// => "api/customer?p_filter=new"
The ParameterTransformerAttribute
is actually not only constrained to parameters. It can be used on methods or the interface as well. If it is used on a method, it will be applied to all parameters in the method. If used on the interface, it will be applied to all parameters in all methods.
[BaseLocation("api/customer")]
public interface ICustomerApi
{
[Get("")]
[Prefix("p_")]
Task<HttpResponseMessage> GetCustomers(string filter = null, int? age = null);
}
await api.GetCustomers("new", 28);
// => "api/customer?p_filter=new&p_age=28"
[BaseLocation("api/customer")]
[Prefix("p_")]
public interface ICustomerApi
{
[Get("")]
Task<HttpResponseMessage> GetCustomers(string filter = null, int? age = null);
}
await api.GetCustomers("new", 28);
// => "api/customer?p_filter=new&p_age=28"
The main objective of a parameter-transformer is to modify the passed Parameter-object. The two most often modified properties of this object are Name
and Values
. Another important property is the ParameterType-property, which can have four distinct values. Route, Query, Header or Content. This value indicates how you should interpret the Values
and also where these values end up in the created request.
The value of a parameter of the Route
type will replace any {xxx}
of the url if their name matches "xxx" in this case. Routes can only have one value, so Parameter.Values
typically contains only one element.
The values of a parameter of the Query
type will be appended to the url as query parameters. ParameterName
will be put to the left of the equal sign and ParameterValue
will be put on the right side. If Values
is a list, how it will be ToString()-ed depends on the chosen IQueryParamaterListStrategy
. The default is to just repeat the name for multiple values, like this /api/customers?age=20&age=21
.
The value of Content
(there can only be one content-parameter and that parameter can only have one value) will be serialized and used as body content.
The values of a parameter of the Header
type will be sent as Http Headers. ParameterName
will be used as Header name and Values as values.
Back to the Prefix-attribute example above. How would this work in conjuction with for example the AliasAttribute
? General rule is that the closer your attribute is to the parameters it affects, the later it is applied. That would mean that the AliasAttribute
on parameterlevel would override the PrefixAttribute
on method or interface-level.
[BaseLocation("api/customer")]
[Prefix("p_")]
public interface ICustomerApi
{
[Get("")]
Task<HttpResponseMessage> GetCustomers([Alias("f")]string filter = null, int? age = null);
}
await api.GetCustomers("new", 28);
// => "api/customer?f=new&p_age=28"
Another example
[BaseLocation("api/customer")]
[Prefix("api_")]
public interface ICustomerApi
{
[Get("")]
[Prefix("method_")]
Task<HttpResponseMessage> GetCustomers([Prefix("parameter_")]string filter = null);
}
await api.GetCustomers("new");
// => "api/customer?parameter_method_api_filter=new"
It is also possible to transform the entire list of parameters, in case you for example want to add an extra constant parameter to your call, like this:
public class AddApiKeyAttribute : ParameterListTransformerAttribute
{
private readonly object _apiKey;
public AddApiKeyAttribute(string apiKey)
{
_apiKey = apiKey;
}
public override IEnumerable<Parameter> Apply(IEnumerable<Parameter> parameters, RequestTransformContext requestTransformContext)
{
foreach (var p in parameters)
{
yield return p;
}
yield return Parameter.CreateQueryParameter("api-key", new[] { _apiKey });
}
}
This kind of transformer cannot be placed on a method parameter, but it can be placed on either a web-anchor-method or a web-anchor-type. If placed on a type, it will be applied to all methods in that type.
You can actually go even further. Say you want to apply this api-key-query-string-parameter to all your requests on all your web-anchor-apis. Then you need to implement the interface IParameterListTransformer
instead (or actually you don't, because ParameterListTransformerAttribute
already implements IParameterListTransformer
) and add it to your custom settings implementation that you supply when you create your apis. Like this:
public class MySettings : DefaultApiSettings
{
public MySettings()
{
Request.ParameterListTransformers.Add(new AddApiKeyAttribute("myApiKey"));
}
}
var api = Api.For<IMyApi>("http://myapi.com", new MySettings())
- Getting started
- Basics (Http GET & Base Location)
- Basics pt. 2 (POST/PUT/DELETE, HttpResponseMessage, Exceptions, HttpClient, IDisposable)
- Building an HttpRequestMessage
- Parameter attributes
- Handling responses
- ApiSettings
- Typical setup (Develop/Test/Production & Mocking)
- Logging
- Testing
- Generic Api
- Multipart