WTF Next?

Dev ramblings from a master of nothing.

  Home  |   Contact  |   Syndication    |   Login
  130 Posts | 0 Stories | 77 Comments | 0 Trackbacks

News

INETA Community Speakers Program
GeeksWithBlogs.net: WTFNext's hosting!

View Stacy Vicknair's profile on LinkedIn

Twitter







Tag Cloud


Archives

Post Categories

Community Links

User Groups

Monday, April 30, 2012 #

Since I recently went through this I figured it would be worthwhile to compile the downloads, regedits and other steps into one location.

 

Downloads for Visual Studio 2008

You’ll need to download and install the following for Visual Studio 2008 in this order. You may need to reinstall SP1 if you already have it installed to ensure TFS 2008 behaves properly:

 

Adding the TFS repository

If Visual Studio 2008 will let you, you can now add the server with the following format:

https://tfsserver:port/vdir/projectCollection

If you get TF30335: The server name cannot contain characters ‘/’ or ‘:’ …:

  1. Open Regedit and browse to HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\TeamFoundation\Servers
  2. Add a new string value:
    1. Name – Friendly name that VS2008 will display.
    2. Value - https://tfsserver:port/vdir/projectCollection

Important Note: In VS2010 you don’t have to specify the project collection to connect. Please be mindful that this is necessary for 2008!

 

Resources

http://blog.hinshelwood.com/connecting-vs2008-to-any-tfs2010-project-collection/

http://www.jmedved.com/2009/11/visual-studio-2008-and-team-foundation-server-2010/

http://stackoverflow.com/questions/1925512/visual-studio-2008-cant-connect-to-known-good-tfs-2010

http://social.msdn.microsoft.com/Forums/en-US/tfsgeneral/thread/54c862f1-d93a-4030-8972-60d3dda7d4d4/


Monday, April 16, 2012 #

For the past two years SQL Saturday and Tech Day in Baton Rouge has offered tracks across the Microsoft stack to its attendees. This event is one of the largest SQL Saturdays and attracted 400 registrants last year. We are looking for speakers experienced in SQL Server, .NET development, Sharepoint, Windows IT Professionals and other professional skills that apply to the technology field. If you are interested in speaking please visit http://sqlsaturday.com/150/callforspeakers.aspx and submit your sessions. Submissions close June 5th.


Saturday, March 31, 2012 #

If you’re familiar with SSRS and development you are probably aware of the SSRS web services. The RS utility is a tool that comes with SSRS that allows for scripts to be executed against against the SSRS web service without needing to create an application to consume the service. One of the better benefits of using this format rather than writing an application is that the script can be modified by others who might be involved in the creation and addition of scripts or management of the SSRS environment.

 

Reporting Services Scripter

Jasper Smith from http://www.sqldbatips.com created Reporting Services Scripter to assist with the created of a batch process to deploy an entire SSRS environment. The helper scripts below were created through the modification of his generated scripts.

Why not just use this tool? You certainly can. For me, the volume of scripts generated seems less maintainable than just using some common methods extracted from these scripts and creating a deployment in a single script file. I would, however, recommend this as a product if you do not think that your environment will change drastically or if you do not need to deploy with a higher level of control over the deployment. If you just need to replicate, this tool works great.

Executing with RS.exe

Executing a script against rs.exe is fairly simple.

SNAGHTML7ccebfc

The Script

Half the battle is having a starting point. For the scripting I needed to do the below is the starter script. A few notes:

  • This script assumes integrated security.
  • This script assumes your reports have one data source each.

Both of the above are just what made sense for my scenario and are definitely modifiable to accommodate your needs. If you are unsure how to change the scripts to your needs, I recommend Reporting Services Scripter to help you understand how the differences.

The script has three main methods: CreateFolder, CreateDataSource and CreateReport. Scripting the server deployment is just a process of recreating all of the elements that you need through calls to these methods. If there are additional elements that you need to deploy that aren’t covered by these methods, again I suggest using Reporting Services Scripter to get the code you would need, convert it to a repeatable method and add it to this script!

Public Sub Main()
    CreateFolder("/", "Data Sources")
    CreateFolder("/", "My Reports")
    
    CreateDataSource("/Data Sources", "myDataSource", _
        "Data Source=server\instance;Initial Catalog=myDatabase")
        
    CreateReport("/My Reports", _
        "MyReport", _
        "C:\myreport.rdl", _
        True, _
        "/Data Sources", _
        "myDataSource")
    
End Sub
 
Public Sub CreateFolder(parent As String, name As String)
    Dim fullpath As String = GetFullPath(parent, name)
    
    Try
        RS.CreateFolder(name, parent, GetCommonProperties())
        Console.WriteLine("Folder created: {0}", name)
    Catch e As SoapException
        If e.Detail.Item("ErrorCode").InnerText = "rsItemAlreadyExists" Then
            Console.WriteLine("Folder {0} already exists and cannot be overwritten", fullpath)
        Else
            Console.WriteLine("Error : " + e.Detail.Item("ErrorCode").InnerText + " (" + e.Detail.Item("Message").InnerText + ")")
        End If
    End Try
End Sub
 
Public Sub CreateDataSource(parent As String, name As String, connectionString As String)
    Try
        RS.CreateDataSource(name, parent,False, GetDataSourceDefinition(connectionString), GetCommonProperties())
        Console.WriteLine("DataSource {0} created successfully", name)
    Catch e As SoapException
        Console.WriteLine("Error : " + e.Detail.Item("ErrorCode").InnerText + " (" + e.Detail.Item("Message").InnerText + ")")
    End Try
End Sub
 
Public Sub CreateReport(parent As String, name As String, location As String, overwrite As Boolean, dataSourcePath As String, dataSourceName As String)
    Dim reportContents As Byte() = Nothing
    Dim warnings As Warning() = Nothing
    Dim fullpath As String = GetFullPath(parent, name)
 
    'Read RDL definition from disk
    Try
        Dim stream As FileStream = File.OpenRead(location)
        reportContents = New [Byte](stream.Length-1) {}
        stream.Read(reportContents, 0, CInt(stream.Length))
        stream.Close()
 
        warnings = RS.CreateReport(name, parent, overwrite, reportContents, GetCommonProperties())
 
        If Not (warnings Is Nothing) Then
            Dim warning As Warning
            For Each warning In warnings
                Console.WriteLine(Warning.Message)
            Next warning
        Else
            Console.WriteLine("Report: {0} published successfully with no warnings", name)
        End If
 
        'Set report DataSource references
        Dim dataSources(0) As DataSource
 
        Dim dsr0 As New DataSourceReference
        dsr0.Reference = dataSourcePath
        Dim ds0 As New DataSource
        ds0.Item = CType(dsr0, DataSourceDefinitionOrReference)
        ds0.Name=dataSourceName
        dataSources(0) = ds0
 
 
        RS.SetItemDataSources(fullpath, dataSources)
 
        Console.Writeline("Report DataSources set successfully")
 
 
 
    Catch e As IOException
        Console.WriteLine(e.Message)
    Catch e As SoapException
        Console.WriteLine("Error : " + e.Detail.Item("ErrorCode").InnerText + " (" + e.Detail.Item("Message").InnerText + ")")
    End Try
End Sub
 
 
Public Function GetCommonProperties() As [Property]()
    'Common CatalogItem properties
    Dim descprop As New [Property]
    descprop.Name = "Description"
    descprop.Value = ""
    Dim hiddenprop As New [Property]
    hiddenprop.Name = "Hidden"
    hiddenprop.Value = "False"
 
    Dim props(1) As [Property]
    props(0) = descprop
    props(1) = hiddenprop
    
    Return props
End Function
 
Public Function GetDataSourceDefinition(connectionString as String)
    Dim definition As New DataSourceDefinition
    definition.CredentialRetrieval = CredentialRetrievalEnum.Integrated
    definition.ConnectString = connectionString
    definition.Enabled = True
    definition.EnabledSpecified = True
    definition.Extension = "SQL"
    definition.ImpersonateUser = False
    definition.ImpersonateUserSpecified = True
    definition.Prompt = "Enter a user name and password to access the data source:"
    definition.WindowsCredentials = False
    definition.OriginalConnectStringExpressionBased = False
    definition.UseOriginalConnectString = False
    
    Return definition
End Function
 
Private Function GetFullPath(parent As String, name As String) As String
    If parent = "/" Then
        Return parent + name
    Else
        Return parent + "/" + name
    End If
End Function

Monday, February 27, 2012 #

Last year I had the pleasure of attending one of Markus Egger’s sessions at Houston Techfest. Markus is the President and CSA of EPS Software, creators of CODE magazine and a company that I have a great amount of respect for. The people I’ve met from EPS have consistently been great programmers and speakers who have a commitment to creating great software.

The reason I bring up Markus Egger and EPS Software is because of a particular aspect of their business that challenges me as an individual and as a developer. On Markus’s personal website as well as published in CODE Magazine are several public facing postmortems. That’s right. Public postmortems.

These postmortems outline the successes and failures, the right decisions and the wrong decisions, that were made on real life projects performed by EPS software. This isn’t just a case study that glosses over details and highlights just the positives. I personally am in awe of this. Maybe admitting this says something negative about me, but I think it’s worth saying: I admire the courage and confidence that this concept portrays.

The concept of a public postmortem makes me ask myself the following questions: If I knew my successes and failures would be made public, would I code any differently? Would I have made the same decisions? Am I truly looking to learn from my mistakes? What if my company made public facing postmortems? What would my personal postmortem say on Project XYZ? Can I code without regret?

Software development is a continuous learning process, one that I am confident that the day I believe I have mastered it is the day I should be forcefully retired or committed. Our profession is one of continuous growth and learning, only bested by a continual desire to better oneself. We all have our moments of reflection where we see something we’ve written before and cringe. We’ve all written code that we adamantly hope is never traced back to us. That there’s no developer lurking in the dark alleyways of the internet waiting for the right time to surface and single us out.

The admiration I have is for the pure embrace of the fact that we are imperfect. We will make mistakes and hedge bets that might not turn out as well. There are lessons we can learn if we are willing to hold ourselves accountable for those missed decisions. If we cannot do that, then we will never become the greatest we can be and we will never have the confidence necessary to make the best decisions we can make.

So here’s the call to action. For myself, for anyone who might be listening. Let’s code with the confidence that our decisions were the best we could have made. Let’s reflect afterwards and confirm or adjust our opinions to align with the results we’ve achieved. Let’s learn to code without regret.


Tuesday, January 31, 2012 #

I ran into this particular issue and didn’t find any answers to this specific question online. Here’s how I was able to accomplish this. A couple notes about my scenario:

  • I used slsvcutil.exe to generate my service. I could not get connected to a service through “Add Service Reference” when connecting from outside the ISA server protected connection.
  • This connection was tested and successfully retrieved data from a Windows Phone 7 device. I figured on the scale of success this was the platform that I had the most concern for.
  • This works for services behind ISA Server 2006’s cookie-based authentication.

Step 1: Get the details about the ISA Server POST

Using firebug or a similar tool, take a look at what information happens when you select “Log On” on the ISA Server FBA page. The important information to keep is the Request URL and any Form Data passed in the HTTP POST made when you click “Log On”. The request url should appear in the format https://mysite.com/CookieAuth.dll?Logon

Here’s the form data as reported in Chrome:

image

 

Step 2: Mimic the POST in your code with a provided CookieContainer

First we have to POST to the CookieAuth.dll?Logon in order to get our CookieContainer populated. _container is a private CookieContainer shared between all methods.

   1: Public Sub New()
   2:     InitializeComponent()
   3:  
   4:     Dim logonUrl = "https://mysite.com/CookieAuth.dll?Logon"
   5:     _container = New CookieContainer()
   6:  
   7:     Dim request = CType(WebRequest.Create(logonUrl), HttpWebRequest)
   8:     request.CookieContainer = _container
   9:     request.Method = "POST"
  10:  
  11:     request.ContentType = "application/x-www-form-urlencoded"
  12:  
  13:     request.BeginGetRequestStream(AddressOf RequestCallback, request)
  14: End Sub
  15:  
  16: Private Sub RequestCallback(ByVal ar As IAsyncResult)
  17:     Dim postData = String.Format("curl=Z2F&flags=0&forcedownlevel=0&formdir=3&trusted=4&username={0}&password={1}&SubmitCreds=Log On", "username", "password")
  18:  
  19:     Dim request = CType(ar.AsyncState, HttpWebRequest)
  20:  
  21:     Dim stream = request.EndGetRequestStream(ar)
  22:  
  23:     Using sw As New StreamWriter(stream)
  24:         sw.Write(postData)
  25:     End Using
  26:  
  27:     request.BeginGetResponse(AddressOf ResponseCallback, request)
  28: End Sub

Step 3: Pass the CookieContainer to your service

When using WCF basicHttpBinding make sure that the binding configuration specifies allowCookies=”true”. You can also set this by specifying the same in code against the binding.

   1: Private Sub ResponseCallback(ByVal ar As IAsyncResult)
   2:     Dim request = CType(ar.AsyncState, HttpWebRequest)
   3:     Dim response = request.EndGetResponse(ar)
   4:  
   5:     Dim service = New ListsSoapClient With {.CookieContainer = _container}
   6:  
   7:     AddHandler service.GetListCollectionCompleted, AddressOf GetListCollectionCompleted
   8:  
   9:     service.GetListCollectionAsync(service)
  10: End Sub
  11:  
  12: Private Sub GetListCollectionCompleted(ByVal sender As Object, ByVal e As GetListCollectionCompletedEventArgs)
  13:     Dim result = e.Result.Descendants().Select(Function(x) x.Attribute("Title").Value).ToList()
  14:     Deployment.Current.Dispatcher.BeginInvoke(Sub() ListBox1.ItemsSource = result)
  15: End Sub

Step 4: Profit!

Now you should be good to go get data from your service!

C# Example:

   1: public MainPage()
   2: {
   3:     InitializeComponent();
   4:  
   5:  
   6:     var logonUrl = "https://mysite.com/CookieAuth.dll?Logon";
   7:     _container = new CookieContainer();
   8:  
   9:     var request = (HttpWebRequest) WebRequest.Create(logonUrl);
  10:     request.CookieContainer = _container;
  11:     request.Method = "POST";
  12:  
  13:     request.ContentType = "application/x-www-form-urlencoded";
  14:  
  15:     request.BeginGetRequestStream(RequestCallback, request);
  16: }
  17:  
  18: private void RequestCallback(IAsyncResult ar)
  19: {
  20:     var postData = string.Format("curl=Z2F&flags=0&forcedownlevel=0&formdir=3&trusted=4&username={0}&password={1}&SubmitCreds=Log On", @"username", "password");
  21:     
  22:     var request = (HttpWebRequest) ar.AsyncState;
  23:  
  24:     var stream = request.EndGetRequestStream(ar);
  25:  
  26:     using(var sw = new StreamWriter(stream))
  27:     {
  28:         sw.Write(postData);
  29:     }
  30:  
  31:     request.BeginGetResponse(ResponseCallback, request);
  32:  
  33: }
   1: private void ResponseCallback(IAsyncResult ar)
   2: {
   3:     var request = (HttpWebRequest) ar.AsyncState;
   4:     var response = request.EndGetResponse(ar);
   5:  
   6:     var service = new ListsSoapClient { CookieContainer = _container};
   7:  
   8:     service.GetListCollectionCompleted += GetListCollectionCompleted;
   9:  
  10:     service.GetListCollectionAsync(service);
  11: }
  12:  
  13: private void GetListCollectionCompleted(object sender, GetListCollectionCompletedEventArgs e)
  14: {
  15:     var result = e.Result.Descendants().Select(x => x.Attribute("Title").Value).ToList();
  16:     Deployment.Current.Dispatcher.BeginInvoke(() => listBox1.ItemsSource = result);
  17: }

Monday, November 28, 2011 #

As an individual who does not use Internet Explorer as their primary browser, there is a great feature that you may never notice that allows you to easily copy files to and from a document library: the Open in Windows Explorer link. In browsers such as Chrome or Firefox this link may not appear.

I know this isn’t a major groundbreaking feature, but it’s really easy to overlook and it’s worth knowing about, especially when you need to create a local copy of a full document library. In this quick blog we’ll go over how to access this feature in both SharePoint 2007 and 2010.

First, make sure you are in Internet Explorer. These options may not show in other browsers.

In SharePoint 2007, browse to the document library you would like to access then select Actions > Open with Windows Explorer.

image

In SharePoint 2010, browse to the document library you would like to access then select Library Tools > Library > Open with Explorer from the ribbon.

image


Tuesday, October 25, 2011 #

If you, like me, have ever looked over the MSDN documentation and though how nice it would be to have that type of documentation for your codebase, SandCastle is the tool you’ve been looking for. SandCastle is a Codeplex project initiated by Microsoft in order to provide MSDN-esque documentation for .NET projects or asssemblies. The documentation can come in several formats such as CHM files or a website.

 

GhostDoc and SandCastle Help File Builder

To get started with SandCastle for documentation, I recommend a few additional tools: GhostDoc and SandCastle Help File Builder.

GhostDoc is a Visual Studio extension that allows quick XML documentation to be generated based on naming convention for methods, properties and parameters. XML documentation is the heart of how SandCastle generates documentation, so being able to quickly generate simple comments is a great benefit. The Pro version of GhostDoc also allows for customizations based on T4 templating.

SandCastle Help File Builder is a second Codeplex project that adds a GUI and additional support for SandCastle to make the overall experience of its use even better.

 

Installation

For a comprehensive installation guide, check out the SandCastle Help File Builder Installation Instructions. Their guided installer is easy to use and will verify that everything you need is in the right place. It will also let you know if you are lacking any dependencies you might need for generating specific help file types.

 

Usage

Once you have SHFB installed, open the SandCastle Help File Builder GUI and select File > New Project… then choose a location and name for your project. I follow the conventions for project naming that we use for any other project type and add the project to a subdirectory of the code solution.

image

SHFB saves projects as shfbproj types. These types can be built using MSBuild and incorporated into your current build processes. Do note that the time to generate documentation has a fair amount of overhead to reflect through your generated code.

After you have saved your project you can proceed to customize the sources to document by right clicking Documentation Sources then browsing to either the solution, project, or dll file you would like to include.

SS-2011-10-25_19.58.35

If you are looking to generate an HTML site specifically, this can be set on the HelpFileFormat under the Build section of the project properties.

SS-2011-10-25_20.04.16

Afterwards, just select Documentation > Build Project to create your very own MSDN-esque documentation!

SS-2011-10-25_20.09.45

 

Links

http://sandcastle.codeplex.com
http://shfb.codeplex.com/
http://submain.com/products/ghostdoc.aspx
http://www.ewoodruff.us/shfbdocs/Index.aspx?topic=html/8c0c97d0-c968-4c15-9fe9-e8f3a443c50a.htm
http://broadcast.oreilly.com/2010/09/build-html-documentation-for-y.html


Sunday, October 09, 2011 #

Test Data Builder Pattern

When unit testing we often need to fill any POCO objects with enough data to satisfy the needs of the test. Redoing this in every test can quickly become a burden and slow down the testing process. Test Data Builder is a pattern that’s meant to help in just that situation.

With test data builder you build a fluent interface that allows you to build out your POCO objects by only replacing the properties that you need specific values for. The other values are set to defaults specified by the builder.

Here’s a classic example of test data builder:

Our POCO Object

   1: Public Class Person
   2:     Public Property FirstName As String
   3:     Public Property LastName As String
   4:     Public Property DateOfBirth As DateTime
   5: End Class

Our Test Data Builder Class

   1: Public Class PersonBuilder
   2:     'Set the defaults
   3:     Private _firstName As String = "Default"
   4:     Private _lastName As String = "Default"
   5:     Private _dateOfBirth As DateTime = New DateTime(1999, 12, 31)
   6:  
   7:     Public Function WithFirstName(ByVal firstName As String) As PersonBuilder
   8:         _firstName = firstName
   9:         Return Me
  10:     End Function
  11:  
  12:     Public Function WithLastName(ByVal lastName As String) As PersonBuilder
  13:         _lastName = lastName
  14:         Return Me
  15:     End Function
  16:  
  17:     Public Function WithDateOfBirth(ByVal dateOfBirth As DateTime) As PersonBuilder
  18:         _dateOfBirth = dateOfBirth
  19:         Return Me
  20:     End Function
  21:  
  22:     Public Function Build() As Person
  23:         Return New Person() With
  24:             {
  25:                 .FirstName = _firstName,
  26:                 .LastName = _lastName,
  27:                 .DateOfBirth = _dateOfBirth
  28:             }
  29:     End Function
  30: End Class

Usage example

   1: Dim person = New PersonBuilder() _
   2:     .WithFirstName("Stacy") _
   3:     .WithDateOfBirth(New DateTime(1984, 12, 6)) _
   4:     .Build()

Still a lot of work

Depending on how many objects you need to create builders for and how many properties each object has you can still be in for a lot of upfront work following the test data builder pattern to create that fluent experience.

 

Creating the builder pattern with object initializers

In .NET we have object initializers that can take much of this extra work out by providing an expressive method of setting the object properties. If possible, simply subclassing off of the POCO will prevent us from even needing to write out all of the properties, then we can set default values in the constructor.

Our Subclassed Test Data Builder

   1: Public Class PersonBuilderWithObjectInitializers
   2:     Inherits Person
   3:  
   4:     Public Sub New()
   5:         FirstName = "Default"
   6:         LastName = "Default"
   7:         DateOfBirth = New DateTime(1999, 12, 31)
   8:     End Sub
   9:  
  10:     Public Function Build() As Person
  11:         Return New Person() With
  12:             {
  13:                 .FirstName = FirstName,
  14:                 .LastName = LastName,
  15:                 .DateOfBirth = DateOfBirth
  16:             }
  17:     End Function
  18: End Class

The new builder’s usage

   1: Dim person = New PersonBuilderWithObjectInitializers() With
   2:         {
   3:             .FirstName = "Stacy",
   4:             .DateOfBirth = New DateTime(1984, 12, 6)
   5:         }.Build()

Why still have a build method?

Some of you might be asking, “Why keep the build method when polymorphism should do the work?” Well, good point. The reason I keep the build method is that when working against Entity Framework against POCO objects it will attempt to figure out the true type of the object, not just the type the object is cast as. As a result, it will complain when trying to persist the object. The build method in this case just acts as added assurance that if any type discovery is called against the object you will be safe knowing that the correct type will be discovered.

 

C# examples

Our POCO Object

   1: public class Person
   2: {
   3:     public string FirstName { get; set; }
   4:     public string LastName { get; set; }
   5:     public DateTime DateOfBirth { get; set; }
   6: }

Our Test Data Builder Class

   1: public class PersonBuilder
   2: {
   3:     // Set our defaults
   4:     private string _firstName = "Default";
   5:     private string _lastName = "Default";
   6:     private DateTime _dateOfBirth = new DateTime(1999, 12, 31);
   7:  
   8:     public PersonBuilder WithFirstName(string firstName)
   9:     {
  10:         _firstName = firstName;
  11:         return this;
  12:     }
  13:  
  14:     public PersonBuilder WithLastName(string lastName)
  15:     {
  16:         _lastName = lastName;
  17:         return this;
  18:     }
  19:  
  20:     public PersonBuilder WithDateOfBirth(DateTime dateOfBirth)
  21:     {
  22:         _dateOfBirth = dateOfBirth;
  23:         return this;
  24:     }
  25:  
  26:     public Person Build()
  27:     {
  28:         return new Person
  29:                    {
  30:                        FirstName = _firstName,
  31:                        LastName = _lastName,
  32:                        DateOfBirth = _dateOfBirth
  33:                    };
  34:     }
  35: }

Usage example

   1: //arrange
   2: var person = new PersonBuilder()
   3:     .WithFirstName("Stacy")
   4:     .WithDateOfBirth(new DateTime(1984, 12, 6))
   5:     .Build();

Our Subclassed Test Data Builder

   1: public class PersonBuilderWithObjectInitializers : Person
   2: {
   3:     public PersonBuilderWithObjectInitializers()
   4:     {
   5:         // Set our defaults
   6:         FirstName = "Default";
   7:         LastName = "Default";
   8:         DateOfBirth = new DateTime(1999, 12, 31);
   9:     }
  10:  
  11:     public Person Build()
  12:     {
  13:         return new Person
  14:             {
  15:                 FirstName = FirstName,
  16:                 LastName = LastName,
  17:                 DateOfBirth = DateOfBirth
  18:             };
  19:     }
  20: }

The new builder’s usage

   1: var person = new PersonBuilderWithObjectInitializers
   2:                 {
   3:                     FirstName = "Stacy",
   4:                     DateOfBirth = new DateTime(1984, 12, 6)
   5:                 }.Build();

Thursday, September 29, 2011 #

When we talk about UI Architecture, a common theme of Model-View-Something continues to come up. The question is, however, what is the something? Am I working with Model-View-Controller? Model-View-Presenter? Something else?

The purpose of this blog is to explore the difference between MVC / MVP to give you an understanding of what they mean to accomplish, what are the differences and what situations best cater to each.

 

Does it really matter?

Does the difference really matter between MVC / MVP? Well, in purpose it hardly does. When we implement MVC / MVP we have the same goal: separation of presentation from the data model and synchronization of data between the model and the view. These goals are achieved by both patterns.

In implementation there is a slight difference.

 

Model-View-Controller

The MVC pattern originated in a time where every individual element of a display was managed by custom code. The view was literally the rendered representation of the model and the controller handled the inputs from the user to interact with the model. The model would then inform any concerned views or controllers via the observer pattern. Naturally, MVC lends itself to the situations where the interactions of the user are immediately translated into actions against the controller.

In .NET the ASP.NET MVC framework caters best towards MVC. With ASP.NET MVC the application receives a request for a specific URL. The URL is interpreted to determine a specific Action associated with a specific Controller. The Controller executes the action then returns a View that renders a representation of the model.

 

Model-View-Presenter

MVP was introduced alongside the concept of “widgets” or the more familiar notion of controls. Like the controls we use today in WebForms and WinForms, these early “widgets” were capable of understanding the user input on their own and delegating events as they arise. As a result, in the “widget” space our actions are translated into events that happen on the View itself that must be passed along, rather than handled immediately by the controller. As a result, Views have a tighter coupling to a specific Presenter because of the need to delegate information. This usually lends towards a one-to-one relationship of Presenters and Views.

The presenter in MVP also takes a more “hands on” approach than with MVC’s controller. Usually the presenter will have access to manipulate the view rather than the view talking directly to the model. This leads to the tighter coupling between the presenter and the view in MVP as opposed to MVC, but an interface is usually used as a layer of separation.

As with its origin MVP caters towards the .NET platforms that support built-in controls. This includes both ASP.NET WebForms and WinForms.

 

That’s all, right?

What I hope you’ve gained from this blog is enough to identify the goals and slight differences between MVC and MVP as well as the areas in .NET development where they apply. Check out the references below for a little more information about each and their differences!

References:
http://www.codinghorror.com/blog/2008/05/understanding-model-view-controller.html
http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html
http://martinfowler.com/eaaDev/uiArchs.html
http://haacked.com/archive/2008/06/16/everything-you-wanted-to-know-about-mvc-and-mvp-but.aspx

Technorati Tags: ,,,

Tuesday, August 23, 2011 #

When testing in ASP.NET MVC3, one of the common elements you might want to test is authorization. Although the framework does most of the heavy lifting for ASP.NET Membership and authorization it is still worthwhile to be able to write a test to show that the proper Authorization tag is in place.

You might think that it’s as easy as calling the method on the controller with a proper context, but that’s not the case. When you call the methods directly on the controller the authorizations are skipped. These authorizations are handled by the ActionInvoker when the MVC application runs, so in order to test this via MVC itself would take a lot of cruft. Since we trust that ASP.NET MVC is doing its job correctly it is easier to simply test for the presence of the attributes.

Since this is such a common task I ventured forth to create an extension method that will accomplish exactly what I want with the API I want. Using a lambda syntax most of us are familiar with from mocking frameworks, the final product yields a test method like the following:

   1: <TestMethod()>
   2: Public Sub Register_WhenUserIsInAdministratorRole_ShouldBeAuthorized()
   3:     'Arrange
   4:     Dim target = New AccountController()
   5:  
   6:     'Assert
   7:     target.AssertRoleIsAuthorizedForAction(Sub(a) a.Register(New RegisterModel()), "Administrator")
   8: End Sub

 

Simply pass in a lambda of the action you are attempting to test along with the role you expect authorization to be successful for. The extension method code is as follows:

   1: <Extension()>
   2: Public Sub AssertRoleIsAuthorizedForAction(Of T As Controller)(ByVal controller As T,
   3:     ByVal expression As Expression(Of Action(Of T)), role As String)
   4:  
   5:     'Arrange
   6:     'Get method from expression
   7:     Dim callExpression = TryCast(expression.Body, MethodCallExpression)
   8:     If callExpression Is Nothing Then
   9:         Throw New ArgumentException("Could not determine method from expression", "expression")
  10:     End If
  11:  
  12:     'Act
  13:     Dim roles = callExpression.Method.GetCustomAttributes(GetType(AuthorizeAttribute), inherit:=True) _
  14:         .Cast(Of AuthorizeAttribute)() _
  15:         .Where(Function(a) Not String.IsNullOrEmpty(a.Roles)) _
  16:         .SelectMany(Function(a) a.Roles.Split(",")) _
  17:         .ToList()
  18:  
  19:     'Assert
  20:     Assert.IsTrue(roles.Contains(role))
  21: End Sub

The method takes the given lambda and extracts the method. Then the AuthorizeAttributes for the method that include roles are split into a list of roles. Finally we assert that the list of roles contains our role in question.

In C#:

   1: [TestMethod]
   2: public void Register_WhenUserIsInAdministratorRole_ShouldBeAuthorized()
   3: {
   4:     //arrange
   5:     var accountController = new AccountController();
   6:  
   7:     //assert
   8:     accountController.AssertRoleIsAuthorizedForAction(ac => ac.Register(new RegisterModel()), "Administrator");
   9: }
   1: public static void AssertRoleIsAuthorizedForAction<T>(this T controller, 
   2:     Expression<Action<T>> expression, string role) where T : Controller
   3: {
   4:     //Arrange
   5:     //Get method from expression
   6:     var callExpression = expression.Body as MethodCallExpression;
   7:     if(callExpression == null)
   8:         throw new ArgumentException("Could not determine method from expression", "expression");
   9:  
  10:     //Act
  11:     var roles = callExpression.Method.GetCustomAttributes(typeof(AuthorizeAttribute),true)
  12:         .Cast<AuthorizeAttribute>()
  13:         .Where(a => !string.IsNullOrEmpty(a.Roles))
  14:         .SelectMany(a => a.Roles.Split(','))
  15:         .ToList();
  16:  
  17:     //Assert
  18:     Assert.IsTrue(roles.Contains(role));
  19: }

 

References:
http://stackoverflow.com/questions/669175/unit-testing-asp-net-mvc-authorize-attribute-to-verify-redirect-to-login-page
http://darioquintana.com.ar/blogging/2009/05/23/aspnet-mvc-testing-a-custom-authorize-filters/