Skip to content

Parameter attributes

Mattias Nordqvist edited this page Dec 20, 2017 · 20 revisions

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.

Alias

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);
}

Header

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();

Authorization and AddAuthorization

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();

Content

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.

Custom Parameter Attributes

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

Example: Prefix

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 Parameter object

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.

Route

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.

Query

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.

Content

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.

Header

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.

Transformation precedence

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"

Custom ParameterListTransformerAttributes and ParameterListTransformers

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())