2

I have an API route that uses [FromUri] in combination with a custom class that defines the values that can be passed in via query string params:

[HttpGet]
[Route("sitecore/api/search")]
public IHttpActionResult Search([FromUri] MySearchFilter filter)
{
    ...
}
public class MySearchFilter
{
  public ICollection<ID>? TemplateIds { get; set; }
}

However, whenever I ping /sitecore/api/search?templateIds={xxxx-xxxx}, the TemplateIds value never contains the ID that was specified in the query string, and the value changes every time.

I also tried using short IDs and changing the property type to List<ID> and IEnumerable<ID>. The issue only seems to resolve when I change the property type to IEnumerable<System.Guid>.

Is this an issue with the Sitcore.Data.ID class?

Using Sitecore 10.1.

3
  • 2
    can you try to use : ?TemplateIds ={guid}&TemplateIds ={guid} Jan 5 at 16:55
  • I think you need to create your own converters like here to make it work : learn.microsoft.com/en-us/aspnet/web-api/overview/… Jan 5 at 16:58
  • Guessing it's because you can't instantiate an ID straight from string, needs to be cast to guid first. Whereas a guid can be cast from string, which is the format it is coming in from query string. Jan 5 at 21:39

1 Answer 1

2

This problem is the same as any other model based parameter binding scenario whereby one of your params/properties is a list of some simple type (e.g. List<Guid>). The Sitecore ID class does seem to be treated as a "simple" type, similar to the Guid type in terms of param binding. This makes sense because an ID IS a Guid. This is an important point, because it shows that the ID class is not seen as a complex / custom type by the runtime (which would require custom binding logic).

As the comments mentioned:

  • Formatting the query string as ?TemplateIds ={guid}&TemplateIds ={guid} does work. This is the standard approach when using model binding for list fields. One obvious drawback is that the query string may be longer and more verbose than you would like.
  • Parameter binding also works, although there are simpler options such as...
  • A manual approach:
[HttpGet]
[Route("sitecore/api/search")]
public IHttpActionResult Search(string? templateIds = null)
{
  var filter = new MySearchFilter
  {
    TemplateIds = types.ToIds()
  };
}

Where ToIds() is something like:

public static List<ID> ToIds(this string? value)
{
  if (value != null)
  {
    string[] ids = value.Split(',', '|');

    return ids
      .Select(x => ID.TryParse(x, out ID id) ? id : ID.Null)
      .Where(x => !ID.IsNullOrEmpty(x))
      .ToList();
  }

  return new List<ID>();
}

This way the query string can be formatted as a comma separated string:

?TemplateIds={xxxx},{xxxx}

The benefit of the manual approach is having more control over which params can be accessed if the param model contains many fields, some of which may not be relevant or may be lacking an implementation. The manual approach is simple, flexible, and potentially more secure, but it can result in a good amount of code duplication if this pattern is implemented in many different areas.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.