1

I have my web project in .NET Core 2.x. In the Login Controller, after the login, I save a token in the Session to have this token across pages.

My problem started when I wanted to test all controllers. If I call a controller from a test, HttpContext.Session is null.

Then I found this Mock for Session

public class MockHttpSession : ISession
{
    Dictionary<string, object> sessionStorage = new Dictionary<string, object>();
    public object this[string name]
    {
        get { return sessionStorage[name]; }
        set { sessionStorage[name] = value; }
    }

    string ISession.Id
    {
        get {
            throw new NotImplementedException();
        }
    }
    bool ISession.IsAvailable
    {
        get {
            throw new NotImplementedException();
        }
    }
    IEnumerable<string> ISession.Keys
    {
        get { return sessionStorage.Keys; }
    }

    void ISession.Clear()
    {
        sessionStorage.Clear();
    }

    Task ISession.CommitAsync(CancellationToken cancellationToken 
                                      = default(CancellationToken))
    {
        throw new NotImplementedException();
    }

    Task ISession.LoadAsync(CancellationToken cancellationToken 
                                      = default(CancellationToken))
    {
        throw new NotImplementedException();
    }

    void ISession.Remove(string key)
    {
        sessionStorage.Remove(key);
    }

    void ISession.Set(string key, byte[] value)
    {
        sessionStorage[key] = value;
    }

    bool ISession.TryGetValue(string key, out byte[] value)
    {
        if (sessionStorage[key] != null)
        {
            value = Encoding.ASCII.GetBytes(sessionStorage[key].ToString());
            return true;
        }
        else
        {
            value = null;
            return false;
        }
    }
}

Now, I can access to the session but for every key I have to read its value like

var sessionName = new Byte[20];
bool nameOK = HttpContext.Session.TryGetValue("name", out sessionName);
if (nameOK)
    string result = System.Text.Encoding.UTF8.GetString(sessionName);

Is there another simple way to save some values for a session across controllers and testable?

Update

As you see, with my mock values are in the session but GetString returns System.Byte[] instead of the value.

With Mock With Mock: values are there

2 Answers 2

6

You can use existing ISession extension methods which will probably work with your implementation:

using Microsoft.AspNetCore.Http;

var result = HttpContext.Session.GetString("name");

Check out this class: https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Http.Extensions/SessionExtensions.cs#L32

You can add new as well for your needs

Also check out this question How to mock Session Object in asp net core

UPDATE:

Looks like your TryGetValue is not consistent with extension methods, which will try to store byte[] into your dictionary. The demon is in that line:

value = Encoding.ASCII.GetBytes(sessionStorage[key].ToString()); You are using ASCII, not UTF8, also seems like doing some double converting.

I suggest a change for the method like this:

bool ISession.TryGetValue(string key, out byte[] value)
{
    object storageValue = null;
    if(sessionStorage.TryGetValue(key, out storageValue))
    {
        var bytes = storageValue as byte[];

        if(bytes != null)
        {
            value = bytes;
            return true;
        }

        value = Encoding.UTF8.GetBytes(storageValue.ToString());
        return true;
    }

    value = null;
    return false;
}

You might as well think about using byte[] as dictionary value, not an object

Sign up to request clarification or add additional context in comments.

2 Comments

Using object storageValue = null; won't compile for TryGetValue(). Variable declaration needs to be byte[].
The change done to TryGetValue worked out perfectly... Previously it was returning the type name instead of value and converting that to bytes... Thanks a lot @Curly Brace
1

In my case I had to deal with GetInt32 and SetInt32 extension methods in the unit test. As per the source code below is the implementation

public static void SetInt32(this ISession session, string key, int value)
        {
            var bytes = new byte[]
            {
                (byte)(value >> 24),
                (byte)(0xFF & (value >> 16)),
                (byte)(0xFF & (value >> 8)),
                (byte)(0xFF & value)
            };
            session.Set(key, bytes);
        }

public static int? GetInt32(this ISession session, string key)
        {
            var data = session.Get(key);
            if (data == null || data.Length < 4)
            {
                return null;
            }
            return data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
        }

so following what's mentioned by as answer I had to modify little bit as below to handle it.

 Mock<ISession> sessionMock = new Mock<ISession>();
        var key = "FY";
        int fy = 2020;
        var value =  new byte[]
        {
            (byte)(fy >> 24),
            (byte)(0xFF & (fy >> 16)),
            (byte)(0xFF & (fy >> 8)),
            (byte)(0xFF & fy)
        };

  sessionMock.Setup(_ => _.TryGetValue(key, out value)).Returns(true);

I couldn't find this anywhere so sharing here in case it is helpful for someone who are having same issue.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.