0

I am trying to add one more parameter to my constructors in my Asp.Net Core MVC application, but facing some difficulties to do so. Here is what my implementation looks like.

Login action:

[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public IActionResult Login(LoginViewModel loginModel, string returnUrl = null)
{
   returnUrl = string.IsNullOrWhiteSpace(returnUrl) ? ApiConstants.Dashboard : returnUrl;
   ViewData["ReturnUrl"] = returnUrl;
   if (!ModelState.IsValid) return View(loginModel);
   var token = Service.Login(loginModel);
   if (string.IsNullOrWhiteSpace(token)) return View(loginModel);
   TempData["token"] = token;
   AddCookie(token);
   return RedirectToAction("Index", "Dashboard");
}

private void AddCookie(string token)
{
   HttpContext.Response.Cookies.Append("token", token,new CookieOptions()
   {
      Expires = DateTimeOffset.Now.AddDays(-1)
   });
}

Controller:

private readonly INozzleService _nozzleService;
public NozzleController(INozzleService nozzleService)
{
  var token = HttpContext.Request.Cookies["token"];
  _nozzleService = nozzleService;
}

Nozzle Service:

private static INozzleAdapter Adapter { get; set; }
public NozzleService(INozzleAdapter adapter)
{
  Adapter = adapter;
}

Nozzle Adapter:

private readonly string _token;
public NozzleAdapter(string token)
{
   _token = token;
}

Once I get the token in the adapter, I will be adding the token to the HttpClient header.

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);

ConfigureServices in Startup:

public void ConfigureServices(IServiceCollection services)
{
   services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
   services.AddDistributedMemoryCache();
   services.AddSession();
   services.AddTransient<IAccountService, AccountService>();
   services.AddTransient<IAccountAdapter, AccountAdapter>();
   services.AddTransient<INozzleService, NozzleService>();
   services.AddTransient<INozzleAdapter, NozzleAdapter>();
   services.AddMvc();
}

Can you please let me know what could be the best way to achieve this in Asp.Net core 2.0 MVC application? I have read a post saying that using multiple constructors is not a good idea in Asp.Net Core MVC application, so I don't want to use multiple constructors.

At the same time, I want to make sure all of my classes are unit testable with DI. What should be the best approach here?

Please let me know if anyone needs more information.

Update:

As per Shyju's solution, I was able to implement the cookie, however, I am still in a need to pass two parameters to one of my controllers.

private readonly IAccountService _service;
private readonly ITokenProvider _tokenProvider;
public AccountController(IAccountService service, ITokenProvider tokenProvider)
{
  _service = service;
  _tokenProvider = tokenProvider;
}

So that I can, use the method AddToken as below.

_tokenProvider.AddToken(token);
Sibeesh Venu
  • 18,755
  • 12
  • 103
  • 140
  • How are you injecting the `string token` into the `NozzleAdapter` implementation? – Nkosi May 19 '18 at 16:49
  • @Nkosi Thanks for your comment, I am not sure how can I do that in Asp.Net core 2.0 MVC, if it is just an MVC application(not core) I would have created a different constructor with an injection. – Sibeesh Venu May 19 '18 at 16:51
  • 1
    Did you consider moving the token from the `NozzleAdapter` constructor to it's relevant method which you are calling from the Service ? – Shyju May 19 '18 at 16:59
  • @Shyju Services and Adapters are different class libraries, and most of the actions use this token to get the values from API, so is it a good idea to add a new parameter in all the actions? – Sibeesh Venu May 19 '18 at 17:02
  • 1
    Another option is to create a `ITokenProvider` (with a `string GetToken` method) and it's implementation `CookieTokenProvider` (which will have your existing code to read it from the cookie) and inject that to your Adapter constructor. This way you can provide a mock `ITokenProvider` implementation for unit tests. – Shyju May 19 '18 at 17:05
  • @Shyju I like that idea, thanks. do you have any sample code? – Sibeesh Venu May 19 '18 at 17:07
  • Will post one in few minutes – Shyju May 19 '18 at 17:09
  • I agree with @Shyju suggestion. It will most likely make use of `IHttpContextAccessor` – Nkosi May 19 '18 at 17:11
  • @Shyju Please keep in mind that I am gettiing my token form a different Asp.Net Core Web API solution, which has hosted separately. So I am looking for a solution which will save and use for all the requests to API. – Sibeesh Venu May 19 '18 at 17:11

1 Answers1

3

You may consider abstracting out the logic to get the token to a separate class and inject that as needed.

public interface ITokenProvider
{
    /// <summary>
    /// Gets the token
    /// </summary>
    /// <returns></returns>
    string GetToken();
}

Now create an implementation of this, which will be reading the token from the cookie. Here is a simple implementation, which reads the token from the cookies collection

public class CookieTokenProvider : ITokenProvider
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public CookieTokenProvider(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }
    public string GetToken()
    {
        if (httpContextAccessor.HttpContext.Request.Cookies
                                           .TryGetValue("token", out string tokenValue))
        {
            return tokenValue;
        }
        return null;
    }
}

Now, you can inject the ITokenProvider implementation to anyplace you want and call the GetToken method to get the token value. For example, you may inject this to the NozzleAdapter class constructor.

private readonly ITokenProvider tokenProvider;
public NozzleAdapter(ITokenProvider tokenProvider)
{
   tokenProvider=tokenProvider;
}
public string SomeOtherMethod()
{
   var token = this.tokenProvider.GetToken();
   //Do null check and use it
}

Make sure you register this in the ConfigureServices method in Startup class

services.AddTransient<ITokenProvider, CookieTokenProvider>();

Regarding your comment about getting the token and persisting it, it is up to you where you want to do it. You can do that in the CookieTokenProvider implementation. Read the value and store it somewhere ( a local db, in memory cache etc) and get it from there if exists (the next time)

Now, for your unit tests you can create a MockTokenProvider which does not use HttpContext, but simply return a mock value for your testing,

public class MockTokenProvider : ITokenProvider
{
    public string GetToken() => "FakeToken";
}
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • Thanks a lot for the solution, I have implemented the same, however I still need to pass the new `ITokenProvider tokenProvider` in my Controller, so now I have two parameter to the constructor of the controller. Please see my updated question. – Sibeesh Venu May 20 '18 at 03:45
  • **There is nothing wrong with passing more than one item via constructor injection**. I would suggest injecting/using it in the most needed layer (ex : If you are using the token in the Adapter, inject it there. Also if you are using it in the controller (as you posted in the update to the question), you will not be injecting it. The framework will inject it for you as long as you registered the ITokenProvider -> CookieTokenProvider mapping registered in `ConfigureServices` method. – Shyju May 20 '18 at 03:54
  • Thanks a lot for the comment Shyju. So how can I call the methods of the `CookieTokenProvider`, without injecting in the controller? Can you please explain a bit? Thanks in advance. – Sibeesh Venu May 20 '18 at 05:15
  • 1
    You should use DI. That makes everything unit testable. Why do you want to not inject it ? – Shyju May 20 '18 at 05:23
  • Thanks for the comment Shyju. Sorry for asking too many questions. I am getting confused here, can you please let me know how can I use the functions inside in the `CookieTokenprovider` in `AccountController`? isn't needed a object reference to call that function (`_tokenProvider.AddTokenToCookie(token);`)? And if I need to use it, I should inject it in the `AccountController` right? – Sibeesh Venu May 20 '18 at 07:08
  • Yes. The code you shared in your update 2 (constructor injection) will create an object for you as long as you have the mapping registered in ConfigureService method. – Shyju May 20 '18 at 15:45
  • Yes, I got it. Thanks Shyju. – Sibeesh Venu May 20 '18 at 15:53