Licht
Licht

Reputation: 1109

Recommended way to access the current context from a class library in ASP.NET Core 2.0?

We have separated our business logic out into a singular class library that all other projects reference. Our front end was Web Forms before which had a singleton that made accessing the current web context very easy (HttpContext.Current). In ASP.NET Core 2.0 this is gone and seemingly replaced by Dependency Injection.

DI seems like a step in the right direction to me and I can definitely see the benefit. However with my class library I don't see a way to use DI. So to access all the information needed to fulfill my business requirements I must pass all this information in as parameters.

For example, I record the current user each time I create a new database entry. With my web forms app (which I'm in the process of replacing) my business logic library could could obtain the user like so:

public abstract class TableBase
{
    public User Creator { get; private set; }

    internal TableBase() {}//EF select constructor

    internal TableBase(bool newRow)
    {
        Creator = User.Current;
    }
}

public class Client : TableBase
{
    public string Name { get; set; }

    protected Client() {}//EF select Constructor

    public Client(string name) : base(true)
    {
        Name = name;
    }
}

public class User
{
    public static User Current => GetUserFromContext(HttpContext.Current);
}

This works terrifically since the code to handle Creator is entirely handled by the base class and invisible to derived classes. The derived classes just work. However in ASP.NET Core 2.0 the HttpContext.Current singleton is gone. Apparently replaced by DI. So I can no longer check the currently logged in user the way I was before. I've looked around but can't seem to find a way to access the current web context. So I'm currently thinking that replicating my functionality would work like this:

public abstract class TableBase
{
    public User Creator { get; private set; }

    internal TableBase() {}//EF select constructor

    internal TableBase(User creator)
    {
        Creator = creator;
    }
}

public class Client : TableBase
{
    public string Name { get; set; }

    protected Client() {}//EF select constructor

    public Client(string name, User creator) : base(creator)
    {
        Name = name;
    }
}

public class MyPage : PageModel
{
    private readonly User _currentUser;
    private readonly DbContext _context;

    public MyPage(User currentUser, DbContext context)
    {
        _currentUser = currentUser;
        _context = context;
    }

    public async Task<IActionResult> OnPostAsync(string name)
    {
        var newClient = new Client(name, _currentUser);
        _context.Clients.Add(newClient);
        await _context.SaveChangesAsync();
        return RedirectToPage("/Index");
    }
}

This works but has a few undesirable traits.

Maybe this is a necessary evil. It does also remove the reliance on System.Web that my business logic library previously contained, making it more portable. But it also seems like a lot of work to add when I expected DI to reduce the amount of code I wrote. Are my expectations incorrect? Have I overlooked something?

Upvotes: 3

Views: 1955

Answers (1)

Erik Philips
Erik Philips

Reputation: 54638

Have I overlooked something?

Most likely abstraction and fully embracing DI.

(I almost exclusively use Autofac for DI, including using it instead of Core's implementation and I have no affiliation with that project).

I now have to pass in another parameter (User creator) for the new constructor in all 70 of table entities. This is a lot of added code and we haven't even talked about the fact I have to acquire and pass the parameter at each call site.

Or you can use DI. Let the DI Framework know about the parameters needed and ask it for your Table. It should create the object and pass all the necessary parameters.

I need to use DI to inject the current user into any page model or controller that plans on possibly creating a new object.

See above.

According to the guides I read model binding will initialize a new instance of my table entities on post from the parameters of the post. Wouldn't this use the parameterless constructor and thus leave a null reference in Creator? How can my business logic be applied here? Do I need to build my entity myself from the POST body to utilize logic in the constructor?

See above.

IMHO, the whole point of DI is to say here are things I need, and here are all the things those things need now gimme my object. DI should handle everything else.

Upvotes: 2

Related Questions