Sunday, September 18, 2011

How to Render a partial view in the controller.

I had the need today to render some html using a partial view, and then add that html to a JSON Result in MVC 3. I found a few examples on the internet that does it by extending the Controller class, but I don't particularly like this approach as it seemed hard to test. The reason is that it requires the view to be resolved in the controller itself, using built-in methods that rely on other static methods ("dirty, evil!"). I'm sure you can make it work by mocking some of the funkiness around view engines, but that just seemed like too much hard work.

My solution is to apply a bit of dependency injection by injecting a new class called a PartialViewRenderer as an interface into my controller. This class will abstract the rendering away, thereby removing the dependency on the view engines etc. from the controller. My controller now becomes easily testable again! This also adheres to the default approach of rendering views etc outside of the controller. You can also test the rendering seperately, but I haven't bothered with that.

The code for the renderer is as follows:

public class PartialViewRenderer : IPartialViewRenderer
{
    public string RenderOutput(ControllerContext controllerContext, object model, string partialViewName)
    {
        using (StringWriter writer = new StringWriter())
        {
            ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controllerContext, partialViewName);
            ViewContext viewContext = new ViewContext(controllerContext, viewResult.View, new ViewDataDictionary(model), 
                controllerContext.Controller.TempData, writer);
            viewResult.View.Render(viewContext, writer);
            return writer.GetStringBuilder().ToString();
        }
    }
}

It is used in the following action:

public JsonResult LoadUrl(string request)
{
    RestUrlRequest data = new JavaScriptSerializer().Deserialize(request);
    RestSampleUrlModel model = restExamplesProvider.LoadUrl(data);
    model.OutputHtml = outputRenderer.RenderOutput(ControllerContext, model, formatToOuputViewMap[data.Format]);
    return Json(model, JsonRequestBehavior.AllowGet);
}

outputRenderer is an instance of the PartialViewRenderer that we've injected in this controllers' constructor.

A test can be done as such:

[TestMethod]
public void LoadUrlReturnsUrlResultWithOuputFromRendererForFormat()
{
    RestSampleUrlModel model = new RestSampleUrlModelFixture().Build();
    IRestExamplesProvider samplesProvider = new RestExamplesProviderMockFixture { RestSampleUrlModel = model }.Build();
    RestBrowserControllerFixture controllerFixture = new RestBrowserControllerFixture { RestSamplesProvider = samplesProvider };
    RestBrowserController controller = controllerFixture.Build();
    RestUrlRequest request = new RestUrlRequestFixture { Format = "atom" }.Build();

    JsonResult result = controller.LoadUrl(new JavaScriptSerializer().Serialize(request));

    result.Should().NotBeNull();
    RestSampleUrlModel data = (RestSampleUrlModel) result.Data;
    data.OutputHtml.Should().Be("mock render output");
    controllerFixture.RestOutputRenderer.AssertWasCalled(mock => mock.RenderOutput(controller.ControllerContext, model, "atom"));
}


I used a mock for the implementation of the OutputRenderer in this test. The mock itself is setup in a seperate fixture class. The renderer class itself can be extended to be more flexible with different overloads for the method, but that is surplus to my requirements.

Thursday, July 7, 2011

Fun with CRM RetrieveMultiple, ConditionOperator.Contains and sql indexing

Had an interesting problem today, trying to search a CRM entity.
I had the following code:
QueryExpression query = new QueryExpression("fpis_jobsite")
{ 
    ColumnSet = new ColumnSet { AllColumns = true } 
};
query.Criteria = new FilterExpression();
query.Criteria.AddCondition("fpis_name", ConditionOperator.Contains, siteName);
EntityCollection entities = organizationService.RetrieveMultiple(query);
return entities.Entities.Select(BuildJobSite).ToList();

Running it in a test gave me a Generic SQL Error.

After much fuss, I followed the instructions
here to run a SQL trace.

I found the following error:
"Cannot use a CONTAINS or FREETEXT predicate on table or indexed view 'fpis_JobSite' because it is not full-text indexed."
Once I changed the ConditionOperator.Contains operator in my condition to ConditionOperator.Like, the call executed without any further problems.
On a side note, it is not possible to populate child entities by calling RetrieveMultiple with a QueryExpression. You have to make seperate web service calls. In most cases though, you'd be able to craft your QueryExpression to retrieve all the child entities in a single call.

Wednesday, November 17, 2010

Using a filtered index for a unique constraint

I needed to apply a unique constraint on a combination of columns, but only for rows that had not been marked as deleted. After scouring the web, I came to the conclusion that there were two worthwhile ways to achive this.

  1. Set up a view that only exposes the rows on which the constraint should act. This needs to be a persisted view.
  2. Instead of a constraint, set up a filtered index.

Discussions on Stack Overflow also suggested using triggers to do some magic in the system, but I have a longstanding hatred for triggers and cursors. One could also opt to enforce your uniqueness constraint in all your inserting and updating stored predures, but this is tiresome and error prone.

I opted to do the filtered index, as that seems to be the cleanest way of achieving my goal. Note that filtered indices are only available on SQL 2008. If you have 2005, you'll need to use a different mechanism, like the constraint on the view.

You can find a comparison of unique indices and unique constraints here http://msdn.microsoft.com/en-us/library/aa224827(SQL.80).aspx The writer comes to the conclusion that they're pretty much the same, with the index having a few more creation options.

Sample Code

This is the table we will use to illustrate the effect of the index:

CREATE TABLE dbo.SerialisedStock (
SerialisedStockId INT IDENTITY(1,1) NOT NULL,
StockId INT NOT NULL,
StockCode VARCHAR(50) NOT NULL,
IsDeleted BIT NOT NULL
CONSTRAINT PK_SerialisedStock PRIMARY KEY CLUSTERED
(
SerialisedStockId
)

The code to create our index on the table:

CREATE UNIQUE INDEX UK_SerialisedStock_StockNumber ON SerialisedStock(StockId, StockCode ) WHERE IsDeleted = 0

This insert statement will fail if done more than once:

INSERT INTO SerialisedStock
(StockId, StockCode, IsDeleted)
VALUES(1, 'aa',0)

This insert statement can be done multiple times:

INSERT INTO SerialisedStock
(StockId, StockCode, IsDeleted)
VALUES(1, 'aa',1)

Thursday, October 14, 2010

Cool Visual Studio 2010 Extensions

Came across the PowerCommands extension for VS2010. Does some cool stuff, like


  • giving you a shortcut to reopen recently closed documents (and an optional windows displaying a list of recently closed documents).

  • adds a command on the context menu of the solution explorer that will open up a command prompt with the working directory set to the path of the item you clicked on.

  • Adds a command to open the containing folder of an item clicked on in the solution explorer.

  • copy and paste references.

  • email a code snippet.


http://visualstudiogallery.msdn.microsoft.com/en-us/e5f41ad9-4edc-4912-bca3-91147db95b99


For those of us used to scroll in any direction by dragging with the middle mouse button in almost any Microsoft product, this AutoScroll extension will re-add that functionality to Visual Studio 2010. (This is not an invitation for a debate on class sizes!)

http://vs2010autoscroller.codeplex.com/

There are a couple of other add-ons and extensions that I also use (and won't go without) such as Resharper, Ankh (for svn), TestDriven.Net (though Resharper can do the same stuff) and GhostDoc.

Thursday, March 25, 2010

A tribute to Joel

I haven't been keeping up on my reading list for the past couple of weeks, and have only just read Joel's announcement that he'll be retiring from blogging. It's a sad day, and I feel a little bit like I'm heading to a funeral.

Joel's writing has been a great inspiration to me, and have stimulated many fiery connerdsations at the pub. I still browse back to some of the articles in his archive from time to time. They have the type of content that makes them remain relevant, in the same way as Peopleware remains relevant, many years after being written.

Cheers Joel, and good luck for the future!