0

I'm writing a project in c# asp.net mvc3, and I have a helper-class that looks like this:

using System.Collections.Generic;
using System.Web;

namespace CoPrice.Helpers
{
    public static class Messages
    {
        public static IList<Message> All
        {
            get
            {
                var list = ((IList<Message>)HttpContext.Current.Session["_messages"]) ?? new List<Message>();
                HttpContext.Current.Session["_messages"] = new List<Message>();
                return list;
            }
        }

        public static bool Exists
        {
            get
            {
                return (((IList<Message>)HttpContext.Current.Session["_messages"]) ?? new List<Message>()).Count > 0;
            }
        }

        public static void Add(MessageType type, string message)
        {
            Message m = new Message
            {
                Type = type,
                Text = message
            };
            HttpContext.Current.Session["_messages"] = HttpContext.Current.Session["_messages"] as List<Message> ?? new List<Message>();
            ((IList<Message>)HttpContext.Current.Session["_messages"]).Add(m);
        }

        public enum MessageType
        {
            Info,
            Success,
            Error
        }
        public struct Message
        {
            public MessageType Type;
            public string Text;
        }
    }
}

However, when I try to use these in a test, it crashes (cause of HttpContext.Current beeing null). How can I make this work both in tests and in the app itself? I don't mind having to change this class to use something else than HttpContext.Current to access the session, but I want it to have properties as it does, so it can't take the session-object as a parameter.

Any ideas on how to solve this problem?

Alxandr
  • 12,345
  • 10
  • 59
  • 95

3 Answers3

2

You need to define an IMessagesProvider and use an DI container to inject the implementation of the IMessagesProvider. In real use you'll have an implementation that uses the ASP.Net session. In test use you'll mostly mock it. BTW, you probably shouldn't have a static Messages class. In fact, IMessagesProvider will probably replace your static Messages class.

Ex:

public class FooController : Controller
{
    IMessagesProvider _messages;

    public FooController(IMessagesProvider messages)
    {
        // Your DI container will inject the messages provider automatically.
        _messages = messages;
    }

    public ActionResult Index()
    {
        ViewData["messages"] = _messages.GetMessages(Session.SessionId);
        return View();
    }
}

Mind you, this is a very simple example. In fact, you probably want one more class which is the model that drives the view. A quick intro:

public ActionResult Index()
{
    var model = PrepareModel(_messages);
    return View(model);
}

"PrepareModel" is your very own method that instantiates a new model class, fills it with the necessary data, and then you send it off to your view. It's typical to have one model class per view defined. E.g. you'd have model classes like "SignupFormModel", "DashboardModel", "ChatModel", etc., Doing so allows you to have strongly-typed views as well (a great thing).

xanadont
  • 7,493
  • 6
  • 36
  • 49
  • Hmm. Can you provide more of an example how to do this? I haven't used much IOT at all. And also, I need to be able to use this message-provider from my views (that's why I made it static, cause it's in my layout-file). – Alxandr May 12 '11 at 22:29
  • It's a very large topic. I'd start by looking into AutoFac and how to get ASP.Net MVC going. Just going by the tutorial you'll be introduced to the concept. Also, your Views should *not* have knowledge of your Messages class, this is what your Controller is for. The Controller will choreograph the data sent to the View. – xanadont May 12 '11 at 22:34
  • Yeah, I know that, but that means I need to add the availability of the Messages into EVERY controller, cause it's used by the layout-view. – Alxandr May 12 '11 at 22:36
  • Yes, you do want Messages in EVERY controller ... that's the point. You can probably help your cause by creating your own Controller base class that inherits from Controller and has all the common functionality you want. – xanadont May 12 '11 at 22:44
  • Another tip is you can pull common "view stuff" into a partial view or into the Site.master ... whatever makes most sense. – xanadont May 12 '11 at 22:48
  • I'm using razor, so the layout-page (as I've been saying) is indeed the master-page. But I'll read up on this and see what I'll do. Don't like having to use the viewdata as it isn't typed, but it might be the best thing to do after all... – Alxandr May 12 '11 at 22:58
  • I updated the answer to include the right way to have strongly-typed views. – xanadont May 12 '11 at 23:06
0

You can also implement a mock session object that inherits from HttpSessionStateBase class

Jason
  • 15,915
  • 3
  • 48
  • 72
0

@xanadont is right that you need to turn Messages into a normal class. But (and I know this is heresy in some circles) there's no need for a one-off interface and a full-blown DI framework. Just make the methods on your Messages class virtual so you can mock them in your unit-tests, and use constructor injection:

public class FooController : Controller
{
    Messages _messages;

    // MVC will call this ctor
    public FooController() : this(new Messages())
    {
    }

    // call this ctor in your unit-tests with a mock object, testing subclass, etc.
    public FooController(Messages messages)
    {
        _messages = messages;
    }    
}
Gabe Moothart
  • 31,211
  • 14
  • 77
  • 99
  • True, but in the long run you'll save so much more time using a DI container. When this gets scaled up to a few more controllers and multiple other dependencies requiring injection it will quickly become too burdensome to do all the configuration by hand (and for two different scenarios: run-mode and test-mode). – xanadont May 13 '11 at 14:23