Today in work I had a requirement where I wanted to output binary content to the response output stream.  I also want to stay with the MVC Controller for serving this content and so wanted a result I could add my data to and return it.  To my knowledge there is a ContentResult, but with this the Content property is of type string, and is not what I wanted.  I this particular case, I am physically GZipping content and then writing out to the Response.OutputStream

Previous to this I have been seeing and using examples where you assign a GZipOutputStream as a Filter on the Response dynamically.  For some strange reason on the Live environment the header and filter where being ignored on the response yet on the test, builds and sandbox everything worked as expected and the dynamic resources got GZipped.

So I had to go another way and what I came up with was to physically GZip the output content and send it to the response with the accompanying header.  I cannot see why this would not work on the server as the content is physically being served already GZipped opposed to applying a filter which can obviously be removed somewhere in the pipeline.  Any way after some reading I found some evidence that a library, http://www.icsharpcode.net/OpenSource/SharpZipLib/ , yields far better results than the out of the box one from .NET, so for this example I have a dependency on this assembly.  Apart from that I have added a couple of properties which allow for the conditional Compression and also a list of headers to give a little more flexibility.  So here is the code:

    /// <summary>
    /// A content result which can accept binart data and will write to the output
    /// stream.  If GZip is set to true the content will be GZipped and the relevant
    /// header added to the response HTTP Headers
    /// </summary>
    public class BinaryContentResult : ActionResult
    {
        public byte[] Data { get; set; }
        public NameValueCollection Headers { get; set; }
        public bool Gzip { get; set; }


        public override void ExecuteResult(ControllerContext context)
        {
            foreach (string s in Headers.Keys)
            {
                context.HttpContext.Response.AddHeader(s, Headers[s]);
            }

            if (Gzip)
            {
                using (var os = new GZipOutputStream(context.HttpContext.Response.OutputStream))
                {
                    os.Write(Data, 0, Data.Length);
                }
                context.HttpContext.Response.AddHeader("Content-Encoding", "gzip");
                context.HttpContext.Response.AddHeader("X-Compressed-By", "Custom-Compressor");
            }
            else
            {
                context.HttpContext.Response.BinaryWrite(Data);
            }

            context.HttpContext.Response.End();
        }
    }


.NET | ASP.NET MVC | C#
Tuesday, February 16, 2010 7:29:48 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer

I had a requirement on a small home project I am working on, where I need to add some more information to a content result than was there.  More specifically I had to add the status code and also have access to the response header collection.  I did come across an interesting thing with this, where by accessing and adding to the response header collection in a certain way will actually raise an exception and inform you that this is only support when IIS Integrated Pipeline is enabled.  I did some looking about and found a post by Phil Haack showing how he had achieved what I was looking for when he made the Download result class. 

So the following causes an exception:

public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.StatusCode = (int)StatusCode;
            foreach (string s in Headers.Keys)
            {
                context.HttpContext.Response.Headers.Add(s,Headers[s]);
            }
            base.ExecuteResult(context);
        }

And the following works fine, notice the subtle difference in how the header is added now:

        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.StatusCode = (int)StatusCode;
            foreach (string s in Headers.Keys)
            {
                context.HttpContext.Response.AddHeader(s,Headers[s]);
            }
            base.ExecuteResult(context);
        }

So the full code implementation of the result is below. 

 public class ExtendedContentResult : ContentResult
    {
        public NameValueCollection Headers { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public ExtendedContentResult()
        {
            Headers = new NameValueCollection();
        }

        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.StatusCode = (int)StatusCode;
            foreach (string s in Headers.Keys)
            {
                context.HttpContext.Response.AddHeader(s,Headers[s]);
            }
            base.ExecuteResult(context);
        }
    }

Tuesday, December 01, 2009 12:13:31 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer

I am currently deep into TDD with ASP.NET MVC and Moq as the mocking framework I have chosen.  I have been pondering on some valid methods in order to integrate both page index and also page size into the request. Page index and page size will be unique to each action for the purposes of this blog post and example.  I wanted to have the page index in the url, but the actual page size stored in session.  I have seen in some demonstration apps that the page size has been made constant inside the actual action and unfortunately I feel this is no good as a nice UI feature is being able to see more items per page of a list of items. 

So in the session I have put it and so I wanted to test this out and check that I am infact getting the expected results.  First off I made a simple contract for the paged data called IPagedModel and also an implementing base class called PagedModel:

    public interface IPagedModel
    {
        int TotalCount { get; }
        int TotalPages { get; }
        int PageIndex { get; }
        int PageSize { get; }
    }
    public class PagedModel : IPagedModel
    {
        private int _totalCount;
        private int _pageIndex;
        private int _pageSize;

        public PagedModel(int pageIndex, int pageSize, int totalCount)
        {
            _totalCount = totalCount;
            _pageIndex = pageIndex;
            _pageSize = pageSize;
        }

        #region IPagedModel Members

        public int TotalCount
        {
            get { return _totalCount; }
        }

        public int TotalPages
        {
            get { return (int)Math.Ceiling((decimal)_totalCount / (decimal)_pageSize); }
        }

        public int PageIndex
        {
            get { return _pageIndex; }
        }

        public int PageSize
        {
            get { return _pageSize; }
        }

        #endregion
    }

And the name of the test is this:

[TestMethod]
public void IndexAction_For_Space_1_Page_2_PageSize_2_Should_Have_PageIndex_1_PageSize_2_TotalCount_9()

I want to submit the required page number to the controller using a base 1 index and then using it inside the controller action using a zero based index. The method for the Index action which I will be testing expects an id for the object which it will be targeting but also a page number which is a nullable type in case it is not found inside the url and so will default to 0.  It is in this action where I am using the Session object to get the desired page size for the action. 

        public ActionResult Index(int id, int? pageIndex)
        {
            if (pageIndex == null)
                pageIndex = 0;

            if (pageIndex > 0)
                pageIndex = pageIndex - 1;

            int pageSize = Session["SpaceController!Index!PageSize"] == null ? 10 : Convert.ToInt32(Session["SpaceController!Index!PageSize"]);

            var space = _repository.GetSpace(id);
            if (space == null)
                return View("NotFound");

            long count;

            var forums = _repository.Get(space, pageIndex ?? 0, pageSize, out count);

            return View(new SpaceIndexViewModel(forums, pageIndex ?? 0, pageSize, (int)count));
        }

First thing is to check if the pageIndex is null and if so make it 0.  Next we want to make the conversion from a base 1 index to a base zero index which we will then use against the repository’s paging method, so basically if the page index is greater than 0, we want to minus 1 from it and use that.  We then check the session object for a valid pageSize for the action, specific to the controller.  From there on in I perform repository specific code and send the model onto the view.  To test this I want to be able to mock the controller context but also the session state and also I want to control the value return when a get on the session value "SpaceController!Index!PageSize" is made.  From looking at the Nerd Dinner application I have made a controller method to return me an instance on a controller, a test repository together with pre made test data I know and expect.  I have extended the method slightly so that I can supply both a username to indicate the HttpContext is authenticated but also session name value pairs.  I have used the Pair object for this but the NameValuePair would probably have been better.

        SpaceController CreateSpaceControllerAs(string userName, List<Pair> sessionValues)
        {
            var mock = new Mock<ControllerContext>();
            var mockSession = new Mock<HttpSessionStateBase>();
            mock.Setup(cts => cts.HttpContext.Session).Returns(mockSession.Object);
            mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
            mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

            foreach (Pair sessionValue in sessionValues)
            {
                mock.SetupGet(p => p.HttpContext.Session[sessionValue.First as string]).Returns(sessionValue.Second);
            }

            // Arrange
            var controller = CreateSpaceController();
            controller.ControllerContext = mock.Object;

            return controller;
        }

This tells the controller context to use the mock session object and also to use the supplied values for the expected get calls.  The actual test then consumes this builder method like so.

        [TestMethod]
        public void IndexAction_For_Space_1_Page_2_PageSize_2_Should_Have_PageIndex_1_PageSize_2_TotalCount_9()
        {
            var controller = CreateSpaceControllerAs(FakeForumData.FakeUser.UserName, new List<Pair>(new[]{
                 new Pair{
                      //Session Name
                      First = "SpaceController!Index!PageSize",
                      //Session Value
                      Second = 2
                 }
            }));
            // Act
            //Get first page
            ViewResult result = (ViewResult)controller.Index(1,2);

            IPagedModel model = (IPagedModel)result.ViewData.Model;

            // Assert
            Assert.AreEqual(1, model.PageIndex);
            Assert.AreEqual(2, model.PageSize);
            Assert.AreEqual(9, model.TotalCount);
        }

I am not saying I think this is the best method for storing varying page size personalization by the user, but it is something which has got me mocking the session state successfully so I am happy with that!  I hope this is of some use to others making their way though ASP.NET MVC!

Cheers

Andrew



.NET | ASP.NET MVC | C#
Monday, August 10, 2009 4:43:59 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer