3

I am looking for a bit of constructive advice. I am trying to map how multiple controller endpoints can interact with each other such that I do not end up writing the same code in multiple controllers. Allow me to illustrate with a simple Kanban example.

I am thinking two(or three) controllers here: BoardController, CardController, SubCardController (undecided). For now let's ignore the fact that cards can be organised into lists, for that we would have a ListController sitting between Board and Card controllers.

Board controller:

public class BoardController : ApiController {
    // among basic CRUD methods I have a method that returns single board
    // implementation #1
    // /api/board/getbyid?id=123
    public HttpResponseMessage GetById(int id) {
        var board = dataStore.GetById(id);
        if (board == null)
            return Request.CreateResponse(HttpStatusCode.NotFound);

        return Request.CreateResponse(HttpStatusCode.OK, board);
    }

    // implementation #2
    // /api/board/getbyid?id=123
    public HttpResponseMessage GetById(int id) {
        var board = GetById(id);
        if (board == null)
            return Request.CreateResponse(HttpStatusCode.NotFound);

        return Request.CreateResponse(HttpStatusCode.OK, board);
    }

    [NonAction]
    // normally methods like this one I declare as private
    // but in this case CardController needs to call this method as well
    public static Board GetById(int id) {
        return dataStore.GetById(id);
    }
}

Allow me to clarify that dataStore is a reference to another controller that is solely responsible for data access.

Card controller:

public class CardController : ApiController {
    // among basic CRUD methods I might want to call BoardControllers GetById(id) method (to verify if board exists for example)
    // implementation #1
    public HttpResponseMessage GetAll(int boardId) {
        // call BoardController.GetById(id) by issueing HTTP request
        HttpRequestMessage _request = new HttpRequestMessage(HttpMethod.Get, requestUri);
        HttpResponseMessage _response = Client.SendAsync(_request).Result;
        Board board = _response.Content.ReadAsAsync<Board>().Result;
        if (board == null /* || more condition(s) */)
            return Request.CreateResponse(HttpStatusCode.NotFound);

        // get cards and return
    }

    // implementation #2
    public HttpResponseMessage GetAll(int boardId) {
        // call BoardController.GetById(id) static method
        var board = _boardCtrl.GetById(boardId);
        if (board == null /* || more condition(s) */)
            return Request.CreateResponse(HttpStatusCode.NotFound);

        // get cards and return
    }
}

That's my two alternative solutions. One one hand implementation #1 does not require additional static methods but on the other, I think it is a bad to issue HTTP requests from one controller action to access another controller action.

Let me know what you guys think. Particularly if there is even more neater alternative.

3
  • 3
    create a service and inject that into your controllers to access the models you want. Try to keep your controllers lean. No need for all that logic in the controller. Using the injected service approach allows for it to be reused where needed. Commented Feb 6, 2017 at 12:27
  • @Nkosi That's one quick response. I think I understand what you're saying but I am not sure how to implement it. Would you be able to link me to a good example? Cheers. Commented Feb 6, 2017 at 12:32
  • @Nkosi I think I found what you're reffering to: asp.net/web-api/overview/advanced/dependency-injection Commented Feb 6, 2017 at 12:37

1 Answer 1

2

Create services and inject them into dependent controllers as needed. Controller should be kept lean and not focus on implementation concerns.

public interface IBoardService {
    Board GetById(int id);
    //...other code removed for brevity
}

public class BoardController : ApiController {
    readonly IBoardService dataSource;
    public BoardController(IBoardService dataSource) {
        this.dataSource = dataSource;
    }

    // /api/board/getbyid?id=123
    public IHttpActionResult GetById(int id) {
        var board = dataSource.GetById(id);    
        return board == null ? NotFound() : OK(board);
    }
}

The board service can also be reused to be injected into the CardController if it needs it

public class CardController : ApiController {
    readonly IBoardService boardService;
    readonly ICardService cardService;

    public CardController(ICardService cardService, IBoardService boardService) {
        this.cardService = cardService;
        this.boardService = boardService;
    }   

    public IHttpActionResult GetAll(int boardId) {
        var board = boardService.GetById(boardId);
        if (board == null /* || more condition(s) */)
            return NotFound();

        // get cards from card service and return
    }

}

Using the injected service approach allows for it to be reused where needed.

Also, try to keep your controllers lean.

No need for all that logic in the controller.

The implementation of card service can depend on the board service and expose just a GetAllByBoardId(int id) which will reduce the logic in the CardController like in your example.

Sign up to request clarification or add additional context in comments.

4 Comments

Very neat. Thank you for the example.
Using single responsibility think of the controller's sole purpose is to pass data (model) to view, not implement logic. It should delegate (invert) that control (pardon the pun) out to a service.
Q: You named Board interface as IBoardService. Is it commonly accepted to name these classes with Service suffix? Or can I name mine with whatever suffix I choose; IBoardController or IBoardInfo etc.?
Calling it controller would be misleading. `Service, repository, Context you choose. Once it properly conveys it purpose and avoids confusion.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.