Click here to Skip to main content
15,121,414 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi All,

I am trying to provide the same results from both an existing Web API Controller as well as a new SignalR Hub. I am trying as much as possible not to duplicate the same code in both the Hub and Web API Controller. Any advice or links to materials is greatly appreciated.

What I have tried:

I have tried adding my Web API Controller as part of the constructor of the new Hub but when testing, it was reported that no such Controller exist as part of the Hub.

ExerciseController.cs
C#
namespace WebApplication1.Controllers
{
    [ApiController]
    public class ExerciseController : ControllerBase
    {
        private readonly ILogger<ExerciseController> _logger;
        private readonly Models.APIContext context = new Models.APIContext();

        public ExerciseController(ILogger<ExerciseController> logger)
        {
            _logger = logger;
        }

        // GET: api/v1/<ExerciseController>
        [HttpGet]
        [Route("api/v1/[controller]")]
        public async Task<IEnumerable<WebAPI.APIExercise>> Get()
        {
            List<WebAPI.APIExercise> exercises = new List<WebAPI.APIExercise>();

            List<Models.ModelExercise> modelExercises = await context.ModelExercises.ToListAsync();

            foreach (Models.ModelExercise modelExercise in modelExercises)
            {
                WebAPI.APIExercise exercise = new WebAPI.APIExercise()
                {
                    Id = modelExercise.Id.ToString(),
                    Name = modelExercise.Name,
                    Filename = modelExercise.FileName,
                    ServerIpAddress = modelExercise.ServerIpAddress
                };
                exercises.Add(exercise);
            }

            return exercises;
        }

        // GET api/v1/<ExerciseController>/5
        [HttpGet]
        [Route("api/v1/[controller]/{id}")]
        public async Task<WebAPI.APIExercise> Get(string id)
        {
            bool result = Int32.TryParse(id, out int intId);

            if (result)
            {
                Models.ModelExercise modelExercise = await context.ModelExercises.FirstOrDefaultAsync(ex => ex.Id == intId);
                if (modelExercise != null)
                {
                    return new WebAPI.APIExercise()
                    {
                        Id = modelExercise.Id.ToString(),
                        Name = modelExercise.Name,
                        Filename = modelExercise.FileName,
                        ServerIpAddress = modelExercise.ServerIpAddress
                    };
                }
            }

            return null;
        }
    }
}


ExerciseHub.cs
C#
namespace WebApplication1.Hubs
{
    public class ExerciseHub : Hub<IAPIClient>
    {
        private readonly Controllers.ExerciseController _exerciseController;

        public ExerciseHub(Controllers.ExerciseController exerciseController)
        {
            _exerciseController = exerciseController;
        }

        public async Task<IEnumerable<WebAPI.APIExercise>> Get()
        {
            return await _exerciseController.Get();
        }
    }
}
Posted
Updated 26-Jul-21 3:48am
v2
Comments
SeeSharp2 23-Jul-21 8:38am
   
So, do you have the hub setup? What exactly is the problem?
amsga 23-Jul-21 9:19am
   
I've got a simple Hub based on Microsoft SignalR documentation for server. But I can't seen to call functions from my existing Web API Controller.
SeeSharp2 26-Jul-21 7:25am
   
You should edit your question and add the relevant code so we can see what you are doing.
amsga 26-Jul-21 21:20pm
   
Added in the codes for one of the Controllers and Hubs. Data is taken from a database which is updated from another software, if that matters.
SeeSharp2 27-Jul-21 7:15am
   
OK, so now what exactly is the problem?
amsga 27-Jul-21 7:21am
   
When I run the application, it does not thrown any errors until I try connecting to the Hub. That is when it states that my controller does not exist for some reason. My initial assumption is that the Dependency Injection would cover it.
SeeSharp2 27-Jul-21 10:53am
   
Which line of code and what is the exact error?
Richard Deeming 29-Jul-21 5:57am
   
A lot of your code seems to be related to mapping the Entity Framework entity to the API's data transfer object (DTO). If you're doing that for a lot of classes, I'd be inclined to use something like AutoMapper[^] to do that. Or at least extract the mapping functionality somewhere else so you don't have to repeat the code.

With AutoMapper:
public async Task<IEnumerable<WebAPI.APIExercise>> Get()
{
    return await context.ModelExercises
        .ProjectTo<WebAPI.APIExercise>(Mapper.ConfigurationProvider)
        .ToListAsync();
}

public async Task<WebAPI.APIExercise> Get(string id)
{
    bool result = Int32.TryParse(id, out int intId);
    if (!result) return null;
    
    return await context.ModelExercises
        .ProjectTo<WebAPI.APIExercise>(Mapper.ConfigurationProvider)
        .FirstOrDefaultAsync(ex => ex.Id == intId);
}

1 solution

Do not put logic into the controller.

Typically a web controller should:
1) Take any incoming parameters and convert to whatever data model the domain layer (a.k.a. business logic and other things) needs.
2) Call the domain layer with a single call, passing in the parameters from step 1 - if any.
3) Convert the domain layer response to the format returned by the controller.

In very simple cases (i.e. every example you find on the internet, but VERY few useful examples) you can use the data models of the domain layer directly in the controller skipping step 1) and 3).

The conversion in step 1 and 3 should not contain logic, just plain "dump" copy between data formats as your domain layer will typically have a slightly different model than the controller.

This makes it trivial to have different entry points to your code - for example your SignalR hub, a command line interface or whatever you might need in the future. They will just use their own conversion steps, and then call the exact same method.

It also makes unit testing or Test Driven Development a lot easier.
   
Comments
SeeSharp2 26-Jul-21 7:25am
   
This does not seem to answer the question.
lmoelleb 26-Jul-21 8:54am
   
It looks like an XY problem to me.
Problem X: How do I ensure the SignalR hub and controller runs the same code.
Solution Y: I could call the controller from the SignalR hub. Oh, I can't figure this out, let me ask how it is done (instead of asking how X is done).

So yes, I made an assumption which I probably should not. But then.... neither should you call your controller from your SignalR hub.
amsga 26-Jul-21 21:36pm
   
I am fine if you made the assumption for the XY problem. But I want to further understand why it should not be called from a controller. If I were to move the logic from the Controller to the business layer to be called from both the Controller and the Hub, I would like to understand what else do I need to look out for. My concern is that I would have the same error that I am having now occuring on both ends.
lmoelleb 27-Jul-21 8:05am
   
It is a rather complex issue for a reply to a comment.
Typically you layer your code in three layers. 1) Lowest persistent - database, whatever - this is implementing an interface/base classes allowing you to swap it out. 2) Domain/Business logic. This drives EVERYTHING, and is what you must call to get anything done. It will call the persistence layer as needed. 3) However many frontends you need - for example WebAPI with controllers for a REST API, your signalR hub, a command line tool, ...

As soon as you write a controller it is tied to the REST infrastructure. Your SignalR hub will be tied to the SignalR infrastructure. By moving the actual logic to the domain layer, you avoid the SignalR hub having to pretend it is a REST context as well or the other way around. The more contexts you need to deal with from within a single class, the more complex it gets - and it will get complex fast, once you start thinking about things like authentication/authorization etc.

The trick is that you do not entangle things. A controller necessarely needs to understand what a REST context is. But your SignalR context will be different. So if you try to make one

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900