1
\$\begingroup\$

I am building a RESTful Api service in .NET. I have a requirement where I need to include resource URLs as part of JSON data returned from the endpoints of my API. The solution currently has the following structure:

App.Api (Controllers, Routing etc.)

App.Service (Service Classes, DTOs, Exceptions etc.)

App.Data (Repositories, Entity Framework, Code First migrations etc.)

All DTOs currently have property for storing the URL of the object to allow clients easily navigate to the record. For instance, a DTO with a URL may look like the following, when requested from the API:

{
  "url": "http://localhost:54321/api/contacts/111",
  "title": "Mrs.",
  "firstName": "Scott",
  "lastName": "Stephenson" 
}

Suppose that we have the following controller:

    public class ContactsController : ApiController
    {
        private readonly IContactService _contactService;

        public ContactsController(IContactService contactService)
        {
            _contactService = contactService;
        }

       public async Task<IHttpActionResult> Get()
       {
           // Must set BaseLocation here as "URL" is null in ctor
           _contactService.BaseLocation = Url.Link("DefaultApi", new { controller = "contacts" });

           var contactDtos = await _contactService.GetListAsync();

           return Ok(contactDtos);
       }            

        //POST
        //PATCH
    }

And this service:

    public class ContactService : IContactService
    {
        private readonly IUnitOfWork _unitOfWork;
        private ContactDtoFactory _dtoFactory;
        private ContactDtoFactory TheDtoFactory =>
            _dtoFactory ?? (_dtoFactory = new ContactDtoFactory(BaseLocation));

        public string BaseLocation { private get; set; }

        public ContactService(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public async Task<ContactDto> GetAsync(int id)
        {
             var contact = await _unitOfWork.Contacts.Get(id);
             return TheDtoFactory.Create(contact);
        }

        //CreateAsync
        //etc.
     }

DTO Factory for creating & parsing entities from/to DTOs:

public class ContactDtoFactory
{
    private readonly string _baseLocation;

    public ContactDtoFactory(string baseLocation)
    {
        _baseLocation = baseLocation;
    }

    public ContactDto Create(Contact contact)
    {

        if (string.IsNullOrEmpty(_baseLocation))
        {
            throw new NullReferenceException("Base location missing");
        }

        if (contact == null)
        {
            return null;
        }

        var url = _baseLocation.Contains(contact.Id.ToString()) ? _baseLocation : $"{_baseLocation}/{contact.Id}";

        return new ContactDto
        {
            Url = url,
            FirstName = contact.FirstName,
            LastName = contact.LastName,
            Title = contact.Title
        };
    }

    //Parse
    //Update
    //etc.
 }

}

As you can see in the above architecture, base of a resource URL is generated by the controller, which is passed to the service, and then to the DTO factory for appending the ID at the end.

I tried setting the BaseLocation property of the service within the constructor of the controller, however, the UrlHelper is null. So, I have to set the property in each controller action where I need to return data back to the client, in order to ensure the URLs are calculated properly.

Even though this works fine, I am not entirely convinced that this is the best way to handle this because of code duplication in each controller action. I would appreciate it if you could point out a cleaner solution to this problem.

UPDATE

The client makes a get call to the Contacts controller using this URL: localhost/api/contacts

This call hits the Get() method in the API controller, where I retrieve the full list of contacts, create DTOs out of them via the Contact Service. I have a class called ContactDtoFactory, which is responsible for creating/parsing domain entities into DTOs and vice-versa. A contact record in the database does not have a URL so the factory must generate it. The response to the Get call will look similar to the following, where each object is a DTO.

[
    {
      "url": "http://localhost:54321/api/contacts/111",
      "title": "Mrs.",
      "firstName": "Scott",
      "lastName": "Stephenson" 
    },
    {
      "url": "http://localhost:54321/api/contacts/222",
      "title": "Mr.",
      "firstName": "Joe",
      "lastName": "Bloggs" 
    },
    {
      "url": "http://localhost:54321/api/contacts/333",
      "title": "Mr.",
      "firstName": "John",
      "lastName": "Doe" 
    }
]

The URL is only needed for identifying the location of each record, so it could be used for making a GET, DELETE, PATCH request to that resource. For instance, when the client has the above JSON array, it can directly make calls to url values instead of building up this URL itself. The client can just send a DELETE request to the following URL and expect that it calls the Delete() endpoint on the controller:

http://localhost:54321/api/contacts/333

In the case of a complex DTO with multiple entities, each nested object will also have the resource URL as shown below:

{
  "url": "http://localhost:54321/api/contacts/111",
  "title": "Mrs.",
  "firstName": "Scott",
  "lastName": "Stephenson",
  "organisation": {
     "url" : "http://localhost:54321/api/organisations/123",
     "name": "X Corp."
  }  
 }
\$\endgroup\$
  • \$\begingroup\$ I think I am a bit confused by the problem definition. I assume you're building a RESTful API service, correct me if I am wrong. If so, when a client makes a GET call, the request URL may already match the value recorded in resource's DTO. This is not true if the resource is accessed by an "indirect" URL, e.g. /petowners/1/cat may return a Cat document with ulr: '/cats/42'... For POST this url value is more obvious. With the case of UPDATE, the client often have the correct URL on hand... Could you provide several examples of request URLs with different verbs and expected result DTO URLs \$\endgroup\$ – Igor Soloydenko Apr 1 '17 at 0:40
  • \$\begingroup\$ What is the JSON supposed to look like for complex DTOs that aggregate other DTOs in their bodies? Please click to see this example \$\endgroup\$ – Igor Soloydenko Apr 1 '17 at 0:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.