0

My controller action to update user status in mvc:

public class UserController : AdminController
{
    public async Task UpdateUserStatus(int id, int status)
    {
        await UpdateTheStatus<UserService, User>(id, status);
    }
}

I have a base controller to update the user status

public abstract class AdminController : ControllerBase
{
    public async Task UpdateTheStatus<TService, T>(int id, int status)
        where TService : StatusService<T>, new()
    {
        await new TService().UpdateStatus(id, status);
    }
}

My UserService.cs have constructor

public class UserService : StatusService<User>
{
    public UserService(MyContext context) : base(context)
    {
    }

    ....
}

Base class StatusService.cs

public abstract class StatusService<T>
{
    protected MyContext ctx = null;

    public StatusService(MyContext context)
    {
        ctx = context;
    }

    public async virtual Task UpdateStatus(int id, int status)
    { 
        ...

        await ctx.SaveChangesAsync();
     }
}

Then my code have the error:

'UserService' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TService' in the generic type or method

How do I pass the constructor with parameter as generic type?

Howard Hee
  • 909
  • 1
  • 13
  • 29
  • 1
    Where do you want `UpdateStatus()` to get the `context` parameter from? Either define a parameterless constructor, or pass in a factory function `Func` – Charlieface Aug 25 '22 at 09:44
  • I am using .NET core, define the context on Program.cs, then DI the context on controller and pass to base class again, so it's unable create a parameterless constructor. How do I use `Func`? – Howard Hee Aug 25 '22 at 10:03
  • Perhaps you should be using Dependency Injection? Where does this `context` come from anyway – Charlieface Aug 25 '22 at 10:03
  • context from my Program.cs, `builder.Services.AddDbContext(options => { options.UseSqlServer("Server=localhost;Initial Catalog=myDB,...."); });` – Howard Hee Aug 25 '22 at 10:06
  • have a look at this question https://stackoverflow.com/questions/840261/passing-arguments-to-c-sharp-generic-new-of-templated-type – Connor Stoop Aug 25 '22 at 10:06

2 Answers2

1

Your fundamental issue is that you need an instance of MyContext at the point where you want to create the TService, as long as you are using a UserService object.

One way to solve this is to pass in a Func<TService> to the UpdateStatus() method instead of specifying the new restraint:

public async Task UpdateStatus<TService, T>(int id, int status, Func<TService> serviceCreator)
    where TService : StatusService<T>
{
    await serviceCreator().UpdateStatus(id, status);
}

Then the implementation of UpdateUserStatus() would look something like this:

public async Task UpdateUserStatus(int id, int status)
{
    var context = new MyContext(); // However you obtain this.
    await UpdateStatus<UserService, User>(id, status, () => new UserService(context));
}

The issue you then face is how to obtain the MyContext instance that you need for creating the UserService object. You can't avoid the need for the MyContext instance because the UserService requires it when you create it.

Here's a runnable example on DotNetFiddle.

You can take a further step of injecting a delegate with which to create the MyContext object:

public async Task UpdateUserStatus(int id, int status, Func<MyContext> contextProvider)
{
    await UpdateStatus<UserService, User>(id, status, () => new UserService(contextProvider()));
}

Now we've pushed the MyContext creation to the outer level. The outer level must still be able to obtain or create a MyContext, of course.

Here's a runnable example with those changes on DotNetFiddle.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Thanks for you answer, but the error still existed, my `UpdateStatus` method is in base class `StatusService.cs` instead of UserService. I updated my code make the structure more clear. Sorry didn't mention it at beginning. – Howard Hee Aug 26 '22 at 04:23
0

Looks like you're set up to use DI in your project, so you should use it.

In your Program.cs, register your classes that implement your base class (which could be better as an interface), eg:

builder.Services.AddTransient<StatusService<User>, UserService>();

Now instead of having a separate task to update the status, just pass your expected service into your constructor via DI which will automatically include the context:

public class WhatIsYourClassCalled
{
    private readonly StatusService<User> _statusService;

    public WhatIsYourClassCalled(StatusService<User> statusService)
    {
        _statusService = statusService;
    }

    public async Task UpdateUserStatus(int id, int status)
    {
        await _statusService.UpdateStatus(id, status);
    }
}
Steve Harris
  • 5,014
  • 1
  • 10
  • 25