Alan Gabriel
Alan Gabriel

Reputation: 53

How to implement a controller action calling another action in a different controller?

I am creating a Web API using ASP.NET core and am confused about how to approach posting to an audit/log table.

There are two tables, an event table, and an event log table. If a user creates a new event, then it would add an entry to both the event and event log table. If a user edits the name/description of an event, it would also create a new entry in the event log table. Notice that the event log table is mostly identical in structure to the event table.

The Event table

+------------------+
|   Event Table    |
+------------------+
| Id               |
| EventName        |
| EventDescription |
| CreatorId        |
+------------------+

The Event Log table

+------------------+
| Event Log Table  |
+------------------+
| Id               |
| EventId          |
| EventName        |
| EventDescription |
| EditorId         |
| TimeEdited       |
+------------------+

Currently, I have two separate controllers in my Web API for each of the two tables, i.e. An event controller and an event log controller.

What currently happens: When there is a post/put for an event, it would create an instance of the event log controller and call the post method for creating an event log, passing in the newly created/updated event log object.

Heres some simplified code...

In EventController.cs..

public IActionResult PostEvent([FromBody] Event event)
{
    _context.Event.Add(event);
    _context.Event.SaveChanges();

    EventLogController logController = new EventLogController(context);
    logController.PostEventLog(event);

    return Ok();
}

In EventLogController.cs...

public IActionResult PostEventLog([FromBody] Event event)
{
    var eventLog = _mapper.Map<EventLog>(event);
    eventLog.TimeEdited = DateTime.Now;

    _context.EventLog.Add(eventLog);
    _context.EventLog.SaveChanges();

    return Ok();
}

While this works fine, for now, I am quite worried about whether or not this is the best practice(It seems bad to create a new controller just to call one method). It may be useful to note that I don't want to send two separate API calls to each controller. I would like the event log to be created as a consequence of adding/updating an entry.

As I see it, I could take some other approaches...

  1. Create a repository/service? for adding a EventLog to the database and inject it into EventController. While this does allow me to avoid creating a controller, I heard that creating a repository is an unnecessary abstraction in asp.net core.

  2. Add the event log entry within the PostEvent action in EventController without creating a controller or repository/service. However, this means that the action method is taking care of two requests.

  3. Continue with the current approach but just inject the EventLog controller instead of creating a new instance.

As for things I don't think I can do...

I don't think I can create a trigger within the database itself since creating an event entry doesn't contain enough information to create an event log (As the database would be unaware of the editor).

I also don't think I can use RedirectToAction as it is a post request that requires something to be sent in the body.

So my question is: What would be the proper way of creating a log/audit entry on each creation/update of the related entity?

Upvotes: 2

Views: 1324

Answers (2)

T.Nylund
T.Nylund

Reputation: 787

A separate class will be your friend here. Of course, if the implementation is very simple and the application is not huge, then there is nothing wrong to create the simplest solution. Anyway, calling another controller will couple your code.

I made a simple example what I prefer similar cases usually.

EventService.cs

public class EventService
{
        private IEventLogger logger;

        public EventService(IEventLogger logger)
        {
            this.logger = logger;
        }

        internal void AddNew(Event @event)
        {

            // Here is your code to persist event.
            // For example, context.SaveChanges(@event)
            // or whatever you want. 

            // When event is saved, I have called object that
            // implements IEventLogger interface. It is just a simple
            // interface to introduce Log(Event @event) -method.
            // 
            // When this service is created, you can pass logger
            // implementation. You can also use different way to pass
            // right implementation if constructor isn't work for you.
            if(logger != null) { 
                this.logger.Log(@event);
            }
        }
}

EventController

# In your controller you can use service like this.
# Of course, service is not needed to create inside the action
# but I just wanted to make a simple example.

public IActionResult PostEvent([FromBody] Event @event)
{
    IEventLogger logger = new EventLogger();
    EventService eventService = new EventService(logger);

    eventService.AddNew(@event); 

    return Ok();
} 

Why to use service?

It should not be a controller that know the details of implementation. As said before, the controller just controls.

Here, I introduced a service that is responsible to handle persist events. You can initialize service with context. I keep the example simple and not introduce any more abstraction but you probably get the idea.

When method AddNew(event) is called, then the service will know about your business rules. In your case, I see it like that "Logging" is one of the rules that you need. You want to keep a log of changes so using service, you always have logger available.

IEventLogger is a simple interface. When you implement it, you can use the same context inside it and then handle logging inside the implementation.

if(logger != null) is not a mandatory but usually good way to have an option to not use logging if not needed.

If needed, you can always have separated EventLogController that can return logged data to use from the API. Your EventLogController have method PostEventLog. Now, if you want, you can use the same logger inside the controller action to persist log event.

Upvotes: 0

Al Kepp
Al Kepp

Reputation: 5980

I would prefer the creation of a separate class, what you actually mention in your "other approach 1". You can call it from multiple places then.

Generally, as you know, many approaches are possible. Rule of thumb should always be: "Controller should only control, it should not do the work itself." This means that the controller should only know what to call to do the work. If you follow this principle, you put your actual code to a separate class and then you can call it from multiple places of one or many controllers as needed.

Upvotes: 1

Related Questions