Using Facebook Connect with ASP.NET MVC and the Facebook Developer Toolkit 3


If you haven’t heard, Microsoft and Clarity Consulting have released version 3 of the Facebook Developer Toolkit (FDT). As described on the Codeplex home page the main goals for version 3 were:

Provide better doc and samples
• Provide support for Silverlight
• Provide support for ASP.NET MVC
• Provide improved support for WPF
• Provide improved support for FBML (FBML Server Controls)
• Provide a login control that can be used to replace the BasePage and/or MasterPage for Canvas Development
• Improve out of the box support for Extended Permission Prompts
• Refactor core source to improve maintainability and design
• Fix known bugs

Although several great examples are shipped with the latest version of FDT, none of them target authenticating users via Facebook Connect on ASP.NET MVC apps. Luckily, you’ll find that ASP.NET MVC apps aren’t all that different from Web Form apps in this regard.

Goals

  • The goals of this contrived example are:
  • Host a “Connect with Facebook” button in the Index view of our HomeController.
  • Popup the Facebook Connect page when the button is clicked.
  • Upon successful login, redirect back to the AuthenticateFacebook action on the HomeController so we can verify the user is in fact logged in and grab some user info.
  • Redirect to the About view on the HomeController and display some personal information from Facebook Connect.

Assumptions

Before we begin, I’m going to make the following assumptions:

  • You’ve already created an ASP.NET MVC project with which you want to authenticate users via Facebook Connect
  • You’ve already downloaded FDT 3 and added a reference to Facebook.dll to your ASP.NET MVC project
  • You’ve already created a Facebook application using the Developer application on Facebook.
  • You’ve made a note of your Facebook application’s API Key and Application Secret.

Index View

Setting up the Index view is a pretty trivial task. The first order of business is to add a reference to the Facebook JavaScript file. Simply add this to the <head></head> tag of your view (or most likely your master page):

Code Snippet
  1. <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>

The next thing we need to do is add the Facebook Connect button on the view:

Code Snippet
  1. <a title="Facebook" href="#" onclick="FB.Connect.requireSession(function(){window.location='/Home/AuthenticateFacebook';}); return false;"><img alt="Connect with Facebook" src="http://static.ak.fbcdn.net/images/fbconnect/login-buttons/connect_light_large_long.gif"></a>

Notice how the FB.Connect.requireSession accepts a function which is what happens upon a successful login by the user. In this instance we’re redirecting the browser to the AuthenticateFacebook action on the HomeController, but you can change this to whatever you like.

The last thing we need to do is add the required FB.Init JavaScript call at the very bottom of our Index view:

 

Code Snippet
  1. <script type="text/javascript">
  2.     FB.init('00000000000000000000000000000000', 'XdReceiver');
  3. </script>

As stated on the Facebook Developer Wiki, this call safely initializes the Facebook API for use on a Connect site or iframe application. The first parameter is your application’s API key, while the second URL of our cross domain communication channel file which we’ll be creating next.

XdReceiver

For security reasons, web browsers restrict web sites from making AJAX calls across domains. In general this is a very GOOD thing but can be a bit of  a headache when trying to integrate your site with an external site. Hoverever, there are ways around this – in Facebook’s case they use an iframe (i.e. XdReceiver). You can read all the gory details about it here.

Setting up our XdReceiver involves two simple steps. First, open up HomeController.cs and add the following action:

Code Snippet
  1. public ActionResult XdReceiver()
  2. {
  3.     return View();
  4. }

Next, add a view named XdReceiver to the Home folder in your Views folder - be sure to uncheck the Select master page on the Add View dialog. Replace the entire contents of the view with the following:

Code Snippet
  1. <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4.     <body>
  5.         <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js" type="text/javascript"></script>
  6.     </body>
  7. </html>

AuthenticateFacebook Action

Up until now we’ve been laying the foundation for our Facebook Connect application - now onto the good stuff! The AuthenticateFacebook action on the HomeController is where all the magic happens.

Code Snippet
  1. public ActionResult AuthenticateFacebook()
  2. {
  3.     
  4.     ConnectSession _connectSession = new ConnectSession("00000000000000000000000000000000", "00000000000000000000000000000000");
  5.  
  6.     if (!_connectSession.IsConnected())
  7.     {
  8.         // The request does not contain the details of a valid Facebook connect session - you'll probably want to throw an error here.
  9.     }
  10.     else
  11.     {
  12.         // Authenticated, create API instance.
  13.         Api _facebookAPI = new Api(_connectSession);
  14.  
  15.         // Load user.
  16.         user user = _facebookAPI.Users.GetInfo();
  17.  
  18.         string fullName = user.first_name + " " + user.last_name;
  19.         DateTime dob = DateTime.ParseExact(user.birthday, @"MMMM d, yyyy", System.Globalization.DateTimeFormatInfo.InvariantInfo);
  20.         long userId = _connectSession.UserId;
  21.  
  22.         TempData["FullName"] = fullName;
  23.         TempData["Dob"] = dob;
  24.         TempData["UserId"] = userId;
  25.  
  26.         return RedirectToAction("About", "Home");
  27.     }
  28.  
  29.     return RedirectToAction("Index", "Home");
  30.     
  31. }

For the most part, the AuthenticateFacebook action is straight forward. We first create a ConnectSession using our API Key and Application Secret. If the user was successfully authenticated the session will be connected which means we can then create an instance of the Api and use that to access information about the user. In this case, we’re retrieving the user’s name, date of birth, and user id and then redirecting them to the About view on the HomeController.

About View

The about view simply displays the user’s name, date of birth, and user id that we stuffed into the TempData collection.

Code Snippet
  1. <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
  2.  
  3. <asp:Content ID="aboutTitle" ContentPlaceHolderID="TitleContent" runat="server">
  4.     About Us
  5. </asp:Content>
  6.  
  7. <asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
  8.     <p><%=TempData["FullName"]%>, you were born on <%=TempData["Dob"]%> and your Facebook user ID is: <%=TempData["UserId"]%></p>
  9. </asp:Content>

Conclusion

Hopefully this sample shows you just how easy it is to add Facebook Connect to your ASP.NET MVC. In a real world application you’ll probably want to create a user account in your site’s membership system (Membership Provider for instance) and then map the user’s Facebook Connect user id to your site’s user id.

I’ve zipped up a sample which you can download here: MvcFBConnectSandbox.zip

author: Ryan Graham | posted @ Sunday, November 22, 2009 1:15 AM | Feedback (41)

Reusable, Maintainable, and Extensible WCF Security


Lately I’ve been having a lot of fun architecting and developing a Silverlight project at work that uses WCF to communicate with a backend CMS system. One of the challenges however, was coming up with a reusable, maintainable, and extensible means of applying security to the WCF services. My solution just happens to be the topic of this post.

Luckily, I stumbled upon a post by David Betz (Understanding WCF Services in Silverlight 2) in which he walks the reader through a thorough tutorial on the proper way of using WCF with Silverlight. If you haven’t already read Understanding WCF Services in Silverlight 2 I recommend you do so – especially since the remainder of this post will build upon the SecurityOperationBehavior and SecurityOperationInvoker classes David introduces towards the middle of his post. I’d also strongly encourage you to read a prior post by David (Creating Streamlined, Simplified, yet Scalable WCF Connectivity), which serves as the foundation for his Understanding WCF Services in Silverlight 2 post.

Go ahead and read those two posts, I’ll wait for you right here.

Alright, now that you’ve read and understand David’s posts, let’s pick-up where he left off:

Using The SecurityOperationBehavior and SecurityOperationInvoker Classes

With the knowledge you’ve gained by reading David’s posts you should be able to setup a WCF project with operations secured like so:

Service Contract:

 

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.ServiceModel;
  5. using System.Text;
  6.  
  7. namespace Rjygraham.WcfSecurity.Services.ServiceContracts
  8. {
  9.  
  10.     [ServiceContract]
  11.     public interface ITestService
  12.     {
  13.  
  14.         [OperationContract(AsyncPattern = true)]
  15.         IAsyncResult BeginFoo(AsyncCallback callback, object state);
  16.  
  17.         string EndFoo(IAsyncResult result);
  18.  
  19.     }
  20. }

 

Service Implementation (notice the [SecurityOperationBehavior] attribute from David’s post):

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using Rjygraham.WcfSecurity.Services;
  6. using Rjygraham.WcfSecurity.Services.ServiceContracts;
  7.  
  8. namespace Rjygraham.WcfSecurity.ServiceImplementation
  9. {
  10.     public class TestService : ITestService
  11.     {
  12.  
  13.         [SecurityOperationBehavior]
  14.         public IAsyncResult BeginFoo(AsyncCallback callback, object state)
  15.         {
  16.             CompletedAsyncResult<string> result = new CompletedAsyncResult<string>(state, String.Format("Bar - {0}", DateTime.Now.ToString()));
  17.  
  18.             callback.Invoke(result);
  19.             return result;
  20.         }
  21.  
  22.         public string EndFoo(IAsyncResult result)
  23.         {
  24.             return ((CompletedAsyncResult<string>)result).Data;
  25.         }
  26.  
  27.     }
  28. }

 

Silverlight WCF Client (notice how GetFooAsync method adds the UserName and Password headers):

Code Snippet
  1. using System;
  2. using System.Net;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Documents;
  6. using System.Windows.Ink;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using System.Windows.Media.Animation;
  10. using System.Windows.Shapes;
  11. using Rjygraham.WcfSecurity.Services.ServiceContracts;
  12. using System.ServiceModel;
  13. using System.ServiceModel.Channels;
  14.  
  15. namespace Rjygraham.WcfSecurity.Services.Silverlight.Clients
  16. {
  17.  
  18.     public class TestServiceClient : ClientBase<ITestService>
  19.     {
  20.  
  21.         #region Constructors
  22.  
  23.         public TestServiceClient() : base() { }
  24.  
  25.         public TestServiceClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }
  26.  
  27.         public TestServiceClient(EndpointAddress remoteAddress) : base(new BasicHttpBinding(), remoteAddress) { }
  28.  
  29.         #endregion
  30.  
  31.         #region GetFoo
  32.  
  33.         public void GetFooAsync(Action<string> callback)
  34.         {
  35.             using (OperationContextScope scope = new OperationContextScope((IContextChannel)Channel))
  36.             {
  37.                 MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders;
  38.                 messageHeadersElement.Add(MessageHeader.CreateHeader("UserName", "", "JohnDoe"));
  39.                 messageHeadersElement.Add(MessageHeader.CreateHeader("Password", "", "MyPassword"));
  40.  
  41.                 Channel.BeginFoo(GetFooCallback, callback);
  42.             }
  43.         }
  44.  
  45.         private void GetFooCallback(IAsyncResult result)
  46.         {
  47.             Action<string> callBack = result.AsyncState as Action<string>;
  48.             if (callBack != null)
  49.             {
  50.                 callBack.Invoke(Channel.EndFoo(result));
  51.             }
  52.         }
  53.  
  54.         #endregion
  55.  
  56.     }
  57.  
  58. }

 

 

 

 

And then from your application code:

Code Snippet
  1.  
  2. private void Button_Click(object sender, RoutedEventArgs e)
  3. {
  4.     TestServiceClient client = new TestServiceClient(new EndpointAddress("http://localhost:54936/TestService.svc"));
  5.     client.GetFooAsync(Callback);
  6. }
  7.  
  8. private void Callback(string value)
  9. {
  10.     Deployment.Current.Dispatcher.BeginInvoke(delegate
  11.     {
  12.         TestLabel.Text = value;
  13.     });
  14. }

 

So far, so good, but as you’ll recall from David’s post, the code that does the actual validation of the username and password is embedded deep within the SecurityOperationInvoker class (this is where we retrieve the UserName and Password headers to do validation):

Code Snippet
  1. public Object Invoke(Object instance, Object[] inputs, out Object[] outputs)
  2. {
  3.     //+ authorization
  4.     MessageHeaders messageHeadersElement = OperationContext.Current.IncomingMessageHeaders;
  5.     Int32 id = messageHeadersElement.FindHeader("UserName", "") + messageHeadersElement.FindHeader("Password", "");
  6.     if (id > -1)
  7.     {
  8.         String username = messageHeadersElement.GetHeader<String>("UserName", "");
  9.         String password = messageHeadersElement.GetHeader<String>("Password", "");
  10.         SecurityValidator.Authenticate(username, password);
  11.         //+
  12.         return InnerOperationInvoker.Invoke(instance, inputs, out outputs);
  13.     }
  14.     //+
  15.     throw new FaultException<InvalidOperationException>(new InvalidOperationException(SecurityValidator.Message.InvalidCredentials), SecurityValidator.Message.InvalidCredentials);
  16. }

 

As you can see, this is not an ideal setup.

DataAnnotations to the Rescue

If you’ve worked with DataAnnotations within Silverlight or RIA Services, you might be familiar with the CustomValidationAttribute which allows you to write custom validation logic for your model classes. You might end up with something like the following:

The Person class (note the CustomValidationAttribute declared on the Person class):

Code Snippet
  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.Net;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Documents;
  7. using System.Windows.Ink;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Animation;
  11. using System.Windows.Shapes;
  12.  
  13. namespace Rjygraham.WcfSecurity.SilverlightClient
  14. {
  15.  
  16.     [CustomValidation(typeof(PersonValidator), "ValidateAgeGender")]
  17.     public class Person
  18.     {
  19.  
  20.         [Required]
  21.         public string FirstName { get; set; }
  22.  
  23.         [Required]
  24.         public string LastName { get; set; }
  25.  
  26.         [Range(0d, 100d)]
  27.         public int Age { get; set; }
  28.  
  29.         public bool IsMale { get; set; }
  30.  
  31.     }
  32.  
  33. }

 

The PersonValidator class:

Code Snippet
  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.Net;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Documents;
  7. using System.Windows.Ink;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Animation;
  11. using System.Windows.Shapes;
  12.  
  13. namespace Rjygraham.WcfSecurity.SilverlightClient
  14. {
  15.     public class PersonValidator
  16.     {
  17.  
  18.         public static ValidationResult ValidateAgeGender(Person value)
  19.         {
  20.             if (value.IsMale && value.Age < 18)
  21.             {
  22.                 return new ValidationResult("All males must be 18 years of age or older.");
  23.             }
  24.  
  25.             if (!value.IsMale && value.Age < 21)
  26.             {
  27.                 return new ValidationResult("All females must be 21 years of age or older.");
  28.             }
  29.  
  30.             return ValidationResult.Success;
  31.  
  32.         }
  33.  
  34.     }
  35. }

 

Obviously this is a contrived example, but it illustrates the usefulness and the great extensibility the CustomValidationAttribute class provides DataAnnotations. The initial rendition of the SecurityOperationInvoker is practically begging for the same type of extensibility. In order to do so, we’ll need to modify the SecurityOperationBehavior class to accept a type and method name to pass to the SecurityOperationInvoker class. After some quick Reflectoring of the CustomValidationAttribute class we end up with a SecurityOperationBehavior that looks like this:

Modified SecurityOperationBehavior class:

Code Snippet
  1. using System;
  2. using System.Globalization;
  3. using System.Reflection;
  4. using System.ServiceModel;
  5. using System.ServiceModel.Description;
  6. using System.ServiceModel.Dispatcher;
  7.  
  8. namespace Rjygraham.WcfSecurity.Services
  9. {
  10.  
  11.     [AttributeUsage(AttributeTargets.Method)]
  12.     public class SecurityOperationBehavior : Attribute, IOperationBehavior
  13.     {
  14.  
  15.         #region Fields
  16.  
  17.         private string _cachedErrorMessage;
  18.         private bool _verifiedWellFormed;
  19.         private string _method;
  20.         private Type _validatorType;
  21.  
  22.         #endregion
  23.  
  24.         #region Constructors
  25.  
  26.         public SecurityOperationBehavior(Type validatorType, string method)
  27.         {
  28.             _validatorType = validatorType;
  29.             _method = method;
  30.             string errorMessage = null;
  31.             if (!IsAttributeWellFormed(out errorMessage))
  32.             {
  33.                 throw new InvalidOperationException(errorMessage);
  34.             }
  35.         }
  36.  
  37.         #endregion
  38.  
  39.         #region IOperationBehavior Members
  40.  
  41.         public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
  42.         {
  43.             // Intentionally left empty.
  44.         }
  45.  
  46.         public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
  47.         {
  48.             // Intentionally left empty.
  49.         }
  50.  
  51.         public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
  52.         {
  53.             dispatchOperation.Invoker = new SecurityOperationInvoker(dispatchOperation.Invoker, this._validatorType, this._method);
  54.         }
  55.  
  56.         public void Validate(OperationDescription operationDescription)
  57.         {
  58.             // Intentionally left empty.
  59.         }
  60.  
  61.         #endregion
  62.  
  63.         #region Validation Methods
  64.  
  65.         private bool IsAttributeWellFormed(out string errorMessage)
  66.         {
  67.             if (!_verifiedWellFormed)
  68.             {
  69.                 _verifiedWellFormed = true;
  70.                 _cachedErrorMessage = ValidateTypeParameter();
  71.                 if (_cachedErrorMessage == null)
  72.                 {
  73.                     _cachedErrorMessage = ValidateMethodParameter();
  74.                 }
  75.             }
  76.             errorMessage = _cachedErrorMessage;
  77.             return (errorMessage == null);
  78.         }
  79.  
  80.         private string ValidateTypeParameter()
  81.         {
  82.             if (this._validatorType == null)
  83.             {
  84.                 return @"The SecurityOperationBehavior.ValidatorType was not specified.";
  85.             }
  86.  
  87.             if (!_validatorType.IsVisible)
  88.             {
  89.                 return string.Format(CultureInfo.CurrentCulture, @"The custom validation type '{0}' must be public.", new object[] { _validatorType.Name });
  90.             }
  91.             return null;
  92.         }
  93.  
  94.         private string ValidateMethodParameter()
  95.         {
  96.             if (string.IsNullOrEmpty(this._method))
  97.             {
  98.                 return "The SecurityOperationBehavior.Method was not specified.";
  99.             }
  100.  
  101.             MethodInfo method = _validatorType.GetMethod(_method, BindingFlags.Public | BindingFlags.Static);
  102.             if (method == null)
  103.             {
  104.                 return string.Format(CultureInfo.CurrentCulture, @"The SecurityOperationBehavior method '{0}' does not exist in type '{1}' or is not public and static.", new object[] { _method, _validatorType.Name });
  105.             }
  106.  
  107.             if (method.ReturnType != typeof(bool))
  108.             {
  109.                 return string.Format(CultureInfo.CurrentCulture, @"The SecurityOperationBehavior method '{0}' in type '{1}' must return System.Boolean. Use true to represent success.", new object[] { _method, _validatorType.Name });
  110.             }
  111.  
  112.             ParameterInfo[] parameters = method.GetParameters();
  113.             if ((parameters.Length != 1))
  114.             {
  115.                 return string.Format(CultureInfo.CurrentCulture, @"The SecurityOperationBehavior method '{0}' in type '{1}' must match the expected signature: public static bool {0}(System.ServiceModel.OperationContext context).", new object[] { _method, _validatorType.Name });
  116.             }
  117.  
  118.             if (parameters[0].ParameterType != typeof(OperationContext))
  119.             {
  120.                 return string.Format(CultureInfo.CurrentCulture, @"The SecurityOperationBehavior method '{0}' in type '{1}' must match the expected signature: public static bool {0}(System.ServiceModel.OperationContext context).", new object[] { _method, _validatorType.Name });
  121.             }
  122.  
  123.             return null;
  124.         }
  125.  
  126.         #endregion
  127.         
  128.     }
  129. }

 

And we’ll need to modify the SecurityOperationInvoker like so:

Code Snippet
  1. using System;
  2. using System.Reflection;
  3. using System.ServiceModel;
  4. using System.ServiceModel.Dispatcher;
  5.  
  6. namespace Rjygraham.WcfSecurity.Services
  7. {
  8.  
  9.     public class SecurityOperationInvoker : IOperationInvoker
  10.     {
  11.  
  12.         #region Fields
  13.  
  14.         private MethodInfo _methodInfo;
  15.  
  16.         #endregion
  17.  
  18.         #region Properties
  19.  
  20.         private IOperationInvoker InnerOperationInvoker { get; set; }
  21.  
  22.         public Boolean IsSynchronous
  23.         {
  24.             get { return InnerOperationInvoker.IsSynchronous; }
  25.         }
  26.  
  27.         #endregion
  28.  
  29.         #region Constructors
  30.  
  31.         public SecurityOperationInvoker(IOperationInvoker operationInvoker, Type validatorType, string method)
  32.         {
  33.             this.InnerOperationInvoker = operationInvoker;
  34.             _methodInfo = validatorType.GetMethod(method, BindingFlags.Public | BindingFlags.Static);
  35.         }
  36.  
  37.         #endregion
  38.  
  39.         #region Public Methods
  40.  
  41.         public Object[] AllocateInputs()
  42.         {
  43.             return InnerOperationInvoker.AllocateInputs();
  44.         }
  45.  
  46.         public Object Invoke(Object instance, Object[] inputs, out Object[] outputs)
  47.         {
  48.             if (Validate(OperationContext.Current))
  49.             {
  50.                 return InnerOperationInvoker.Invoke(instance, inputs, out outputs);
  51.             }
  52.             throw new FaultException<InvalidOperationException>(new InvalidOperationException("Unauthorized."), new FaultReason("Unauthorized."));
  53.         }
  54.  
  55.         public IAsyncResult InvokeBegin(Object instance, Object[] inputs, AsyncCallback callback, Object state)
  56.         {
  57.             if (Validate(OperationContext.Current))
  58.             {
  59.                 return InnerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
  60.             }
  61.  
  62.             throw new FaultException<InvalidOperationException>(new InvalidOperationException("Unauthorized."), new FaultReason("Unauthorized."));
  63.         }
  64.  
  65.         public Object InvokeEnd(Object instance, out Object[] outputs, IAsyncResult result)
  66.         {
  67.             return InnerOperationInvoker.InvokeEnd(instance, out outputs, result);
  68.         }
  69.  
  70.         #endregion
  71.  
  72.         #region Private Methods
  73.  
  74.         private bool Validate(OperationContext context)
  75.         {
  76.             bool result = false;
  77.             try
  78.             {
  79.                 MethodInfo info = _methodInfo;
  80.                 result = (bool)info.Invoke(null, new object[] { context });
  81.             }
  82.             catch (TargetInvocationException exception)
  83.             {
  84.                 if (exception.InnerException != null)
  85.                 {
  86.                     throw exception.InnerException;
  87.                 }
  88.                 throw;
  89.             }
  90.             return result;
  91.         }
  92.  
  93.         #endregion
  94.  
  95.     }
  96. }

 

The changes to the SecurityOperationBehavior class are very straight forward, with most changes consisting of validating the validator class and method name. The interesting changes lie in the SecurityOperationInvoker class. In the Invoker class, we grab the MethodInfo of the validator class which we use in the Validate (lines 74-91 in the code snippet above) method called from within the Invoke (lines 46-53) and InvokeBegin (lines 55-62) methods of the Invoker class. You’ll see that the Validate method simply invokes the specified method on the validator class and returns the result. If Validate returns true, we continue to invoke the web service, otherwise we throw a FaultException.

With our modified SecurityOperationBehavior and SecurityOperationInvoker classes we can now create our validator class:

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.ServiceModel;
  6. using System.ServiceModel.Channels;
  7.  
  8. namespace Rjygraham.WcfSecurity.ServiceImplementation
  9. {
  10.     public class TestServiceValidator
  11.     {
  12.         public static bool ValidateUserNameAndPassword(OperationContext context)
  13.         {
  14.  
  15.             MessageHeaders headers = context.IncomingMessageHeaders;
  16.  
  17.             if (headers.FindHeader("UserName", "") > -1 && headers.FindHeader("Password", "") > -1)
  18.             {
  19.                 string userName = headers.GetHeader<string>("UserName", "");
  20.                 string password = headers.GetHeader<string>("Password", "");
  21.  
  22.                 if (userName == "JohnDoe" && password == "MyPassword")
  23.                 {
  24.                     return true;
  25.                 }
  26.             }
  27.  
  28.             return false;
  29.         }
  30.     }
  31. }

 

And modify our service implementation:

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using Rjygraham.WcfSecurity.Services;
  6. using Rjygraham.WcfSecurity.Services.ServiceContracts;
  7.  
  8. namespace Rjygraham.WcfSecurity.ServiceImplementation
  9. {
  10.     public class TestService : ITestService
  11.     {
  12.  
  13.         [SecurityOperationBehavior(typeof(TestServiceValidator), "ValidateUserNameAndPassword")]
  14.         public IAsyncResult BeginFoo(AsyncCallback callback, object state)
  15.         {
  16.             CompletedAsyncResult<string> result = new CompletedAsyncResult<string>(state, String.Format("Bar - {0}", DateTime.Now.ToString()));
  17.  
  18.             callback.Invoke(result);
  19.             return result;
  20.         }
  21.  
  22.         public string EndFoo(IAsyncResult result)
  23.         {
  24.             return ((CompletedAsyncResult<string>)result).Data;
  25.         }
  26.  
  27.     }
  28. }

Summary

By pulling the validation code out of the SecurityOperationInvoker class we’ve made it 100% extensible – just like the CustomValidationAttribute of DataAnnotations. Since every application may use different validation logic having a solution that is extensible is important. Note that we are using Reflection here so there might be some minor performance hits – especially when the service is firing up for the first time. However, since we’re caching the MethodInfo inside the Invoker class, the only performance hit we’ll take when the WCF method is called is the Invoke within the Validate method of the SecurityOperationInvoker.

I hope you find this solution as useful as I did. Feel free to grab the code below and make whatever changes you need.

Rjygraham.WcfSecurity.zip

author: Ryan Graham | posted @ Thursday, October 22, 2009 10:34 PM | Feedback (11)

HTC Imagio Hands-on


Today I posted a review of the HTC Imagio over on Techburgh.com. You can read the review here: http://techburgh.com/blog/2009/10/19/review-verizon-htc-imagio-hands-on/

author: Ryan Graham | posted @ Monday, October 19, 2009 5:46 PM | Feedback (12)

Modifying the DataAnnotationsModelBinder to enable seamless validation in ASP.NET MVC


ASP.NET MVC 2 promises to bring us a seamless client-side/server-side experience much like we currently have in WebForms. However, for those of us developing web apps in MVC 1 we’ve had to resort to rolling our own solution or using a patchwork of validation components. In this post I’ll briefly discuss modifications I made to the DataAnnotationsModelBinder so it would work with DataAnnotations, xVal, and jQuery.Validate.

xVal

If you’re not already familiar with xVal, I’d suggest checking out its Codeplex site and and reading this blog post.

DataAnnotations

The DataAnnotations namespace was introduced in .NET 3.5 SP1 and provides developers the means to declaratively add validation rules to their model classes. For a more in depth look at DataAnnotations and how we’ll be using it here, check out this excellent post by Brad Wilson.

DataAnnotationsModelBinder

In Brad Wilson’s post I linked to above, he mentioned how they had extended the default ASP.NET MVC model binder in the form of the DataAnnotationsModelBinder. Once you’ve downloaded the DataAnnotationsModelBinder and read Brad Wilson’s blog post, take a look at the answer by Martijn Laarman to this question on StackOverflow. I think you’ll agree with the modifications he made to the base DataAnnotationsModelBinder.

jQuery.Validate

jQuery.Validate is in my opinion the best validation plug-in for jQuery and it’s gained wide support from the development community (including Microsoft). The easiest way to include jQuery and jQuery.Validate in your MVC app is to use the Microsoft Ajax CDN.

Modifying the DataAnnotationsModelBinder

Alright, we’ve now covered all the pieces for our patchwork validation solution but there’s one last thing we need to take care of. If you were paying close attention while reading Brad Wilson’s post, you’ll have noticed he mentioned they used a preview version of the .NET 4.0 System.ComponentModel.DataAnnotations assembly while creating the DataAnnotationsModelBinder. Unfortunately, xVal was compiled against the System.ComponentModel.DataAnnotations assembly that ships with .NET 3.5 SP1 and is therefore incompatible with the DataAnnotationsModelBinder out-of-the-box.

To get the DataAnnotationsModelBinder in shape to work with our solution we need to do the following:

1. Open up the DataAnnotationsModelBinder project

2. Replace the reference to the .NET 4.0 preview System.ComponentModel.DataAnnotations assembly with the .NET 3.5 SP1 version.

3. Modify the OnModelUpdated method from:

 

Code Snippet
  1. protected override void OnModelUpdated(ControllerContext controllerContext,
  2.                                                ModelBindingContext bindingContext) {
  3.             // Base version calls IDataErrorInfo
  4.             base.OnModelUpdated(controllerContext, bindingContext);
  5.  
  6.             // If the model is invalid, don't run model-level validation rules
  7.             if (!ModelIsValid(bindingContext)) {
  8.                 return;
  9.             }
  10.  
  11.             var typeDescriptor = GetTypeDescriptor(bindingContext.Model, bindingContext.ModelType);
  12.             var validationContext = new ValidationContext(bindingContext.Model, null, null);
  13.             validationContext.DisplayName = GetDisplayName(typeDescriptor);
  14.  
  15.             ValidationResult validationResult;
  16.  
  17.             foreach (ValidationAttribute attribute in typeDescriptor.GetAttributes<ValidationAttribute>()) {
  18.                 if (!attribute.TryValidate(bindingContext.Model, validationContext, out validationResult)) {
  19.                     bindingContext.ModelState.AddModelError(bindingContext.ModelName, validationResult.ErrorMessage);
  20.                 }
  21.             }
  22.         }


to:

 

 

Code Snippet
  1. protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
  2.         {
  3.             // Base version calls IDataErrorInfo
  4.             base.OnModelUpdated(controllerContext, bindingContext);
  5.  
  6.             // If the model is invalid, don't run model-level validation rules
  7.             if (!ModelIsValid(bindingContext)) {
  8.                 return;
  9.             }
  10.  
  11.             var typeDescriptor = GetTypeDescriptor(bindingContext.Model, bindingContext.ModelType);
  12.  
  13.             foreach (ValidationAttribute attribute in typeDescriptor.GetAttributes<ValidationAttribute>())
  14.             {
  15.                 if (!attribute.IsValid(bindingContext.Model))
  16.                 {
  17.                     bindingContext.ModelState.AddModelError(bindingContext.ModelName, attribute.ErrorMessage);
  18.                 }
  19.             }
  20.         }

 

4. Modify the OnPropertyValidating method from:

 

Code Snippet
  1. protected override bool OnPropertyValidating(ControllerContext controllerContext,
  2.                                                      ModelBindingContext bindingContext,
  3.                                                      PropertyDescriptor propertyDescriptor,
  4.                                                      object value) {
  5.             string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  6.             var validationContext = new ValidationContext(bindingContext.Model, null, null);
  7.             validationContext.DisplayName = GetDisplayName(propertyDescriptor);
  8.  
  9.             bool result = true;
  10.             ValidationResult validationResult;
  11.  
  12.             foreach (ValidationAttribute attribute in GetValidationAttributes(propertyDescriptor)) {
  13.                 if (!attribute.TryValidate(value, validationContext, out validationResult)) {
  14.                     bindingContext.ModelState.AddModelError(modelStateKey, validationResult.ErrorMessage);
  15.                     result = false;
  16.                 }
  17.             }
  18.  
  19.             return result;
  20.         }

 

to:

 

 

Code Snippet
  1. protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
  2.         {
  3.  
  4.             string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  5.            
  6.             bool result = true;
  7.            
  8.             foreach (ValidationAttribute attribute in GetValidationAttributes(propertyDescriptor))
  9.             {
  10.                 if (!attribute.IsValid(value))
  11.                 {
  12.                     bindingContext.ModelState.AddModelError(modelStateKey, attribute.ErrorMessage);
  13.                 }
  14.             }
  15.  
  16.             return result;
  17.         }

 

5. Modify the GetDisplayName method from:

 

Code Snippet
  1. internal static string GetDisplayName(PropertyDescriptor descriptor) {
  2.             var displayAttribute = descriptor.GetAttribute<DisplayAttribute>();
  3.             if (displayAttribute != null && !String.IsNullOrEmpty(displayAttribute.Name)) {
  4.                 return displayAttribute.Name;
  5.             }
  6.  
  7.             var displayNameAttribute = descriptor.GetAttribute<DisplayNameAttribute>();
  8.             if (displayNameAttribute != null && !String.IsNullOrEmpty(displayNameAttribute.DisplayName)) {
  9.                 return displayNameAttribute.DisplayName;
  10.             }
  11.  
  12.             return descriptor.Name;
  13.         }

to:

Code Snippet
  1. internal static string GetDisplayName(ICustomTypeDescriptor descriptor)
  2.         {
  3.             var displayNameAttribute = descriptor.GetAttribute<DisplayNameAttribute>();
  4.             if (displayNameAttribute != null && !String.IsNullOrEmpty(displayNameAttribute.DisplayName))
  5.             {
  6.                 return displayNameAttribute.DisplayName;
  7.             }
  8.  
  9.             return descriptor.GetClassName().Split('.', '+').Last();
  10.         }

6. Rebuild the DataAnnotationsModelBinder project and reference that assembly in your MVC project.

Basically all we did in making these modifications is remove the .NET 4.0 specific dependencies which will make the DataAnnotationsModelBinder compatible with xVal.

Check out the sample project which shows how to glue all these pieces together and also includes a modified version of the DataAnnotationsModelBinder class.

MvcSandbox.zip

author: Ryan Graham | posted @ Saturday, October 10, 2009 3:26 PM | Feedback (23)

Using the MVC XML Sitemap in your ASP.NET MVC project.


This post is a simple walkthrough to illustrate the usage the MVC XML Sitemap project.

If you haven’t already, grab the source here: http://mvcxmlsitemap.codeplex.com/Release/ProjectReleases.aspx

Once you’ve downloaded it, go ahead and unzip it, open the solution up in Visual Studio and do a rebuild so the assembly is compiled.

A Simple Sitemap Example

  1. Create a new ASP.NET MVC web application (the name is not important) and add a reference to the Cintellect.Web.Mvc.Sitemap assembly to ASP.NET MVC web application you just compiled.
     image
  2. Now open up the HomeController in the Controllers folder and add the following using statement to the class:
    using Cintellect.Web.Mvc.Sitemap;

  3. You’re now able to “opt in” actions in the HomeController by placing a [Sitemap] attribute on the action, like so:
    [Sitemap]
    public ActionResult Index()
    {
    ViewData["Message"] = "Welcome to ASP.NET MVC!";

    return View();
    }

  4. Now create a Sitemap action on the HomeController:
    public ContentResult SiteMap()
    {
    return Content(SitemapFactory.Create(), "text/xml", System.Text.Encoding.UTF8);
    }

  5. Press F5 to start debugging, then navigate to the Sitemap action and you should see something similar to the following:
    <?xml version="1.0" encoding="utf-8" ?> 
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
    <loc>http://localhost:58496/home/Index</loc>
    </url>
    </urlset>

[Sitemap] Attribute Named Parameters

When you added the [Sitemap] attribute to the Index action, you might have noticed there are a number named parameters for which you can specify values. Three of these parameters correspond to the XML tags specified in the sitemaps.org protocol, while the others are extended functionality of the Mvc Xml Sitemap framework.

Before we take a look at some more advanced examples, let’s review what each of the named parameters does.

  • ChangeFrequency – Allows you to explicitly indicate the change frequency of the action. Valid values are: always, hourly, daily, weekly, monthly, yearly, never. Please see the sitemaps.org protocolfor more information.
  • LastModified – Ignore this parameter for right now, you can’t specify the value for it in the [Sitemap] attribute.
  • Priority – Allows you to explicitly indicate the priority of this action relative to others on your site. Valid values range from 0.0 to 1.0.
  • IgnoreAuthorizeAttribute – This parameter must be set to true on all actions that have an [Authorize] attribute, and serves merely as verification that you want a protected page URL to be exposed in your sitemap.
  • QueryKey – The sitemap framework allows you to pass the SitemapFactory class delegate functions to create URLs that go deeper than just the controller action. For instance, let’s say you have a Product controller with a Details action that accepts a product id as a parameter in the route (i.e. http://www.yourdomain.com/Products/Details/1), you can pass a function to the SitemapFactory which will retrieve all products for inclusion in the sitemap. This parameter is simply specifies which delegate function should be called for the current action.
  • Route – The sitemap framework will work out of the box with the default {controller}/{action}/{id} route of ASP.NET MVC. However, there are times when you’ll want to specify more advanced routes. This parameter allows you to specify the route which the current action will be executed under so that the URL is generated correctly.
  • SitemapProfile – The sitemap framework allows you to create sitemap profiles in the web.config in which you can provide values for any of the previously discussed named parameters. This parameter tells the framework which profile to load for the action.

I think specifying values for ChangeFrequency and Priority are fairly straight forward, so let’s jump into two more advanced topics:

  • Creating sitemap profiles in the web.config
  • Using QueryKey to specify a delegate to return URLs that are deeper than just the controller action.

Creating Sitemap Profiles in the Web.config

Since many of your actions may share the same sitemap configuration, storing these configurations as a profile in the web.config is a great way to reduce redundant code and provide a way for bulk update. It’s also great because if you need to change a value, you won’t need to recompile your code and redeploy.

To enable sitemap profiles in your web.config, follow these steps:

  1. Modify the <configSections> element to the following (note the inclusion of the new cintellect.web.mvc.sitemap section):
    <configSections>
    <section name="cintellect.web.mvc.sitemap" type="Cintellect.Web.Mvc.Sitemap.SitemapSection, Cintellect.Web.Mvc.Sitemap" />
    <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere"/>
    <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    <section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication"/>
    </sectionGroup>
    </sectionGroup>
    </sectionGroup>
    </configSections>

  2. Add the following section between the <connectionStrings> and <system.web> sections:
    <cintellect.web.mvc.sitemap>
    <sitemapProfiles>
    <clear />
    <add name="HomeWeeklyProfile" route="Default" changeFrequency="weekly" priority="0.7" />
    </sitemapProfiles>
    </cintellect.web.mvc.sitemap>

  3. Now, modify the About action of the HomeController to:
    [Sitemap(SitemapProfile = "HomeWeeklyProfile")]
    public ActionResult About()
    {
    return View();
    }

  4. Press F5 to start debugging, then navigate to the Sitemap action and you should see something similar to the following:
    <?xml version="1.0" encoding="utf-8" ?> 
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
    <loc>http://localhost:58496/home/Index</loc>
    </url>
    <url>
    <changefreq>weekly</changefreq>
    <priority>0.7</priority>
    <loc>http://localhost:58496/home/About</loc>
    </url>
    </urlset>

Notice how the sitemap framework picked up the change frequency and priority which was specified in the profile. Pretty neat, eh?

Using QueryKey to Specify a Delegate to Return URLs

What I’m about to show you next is probably my favorite feature of the framework so far – mainly because it opens up the framework to the developer.

All the work we’ll be doing this time is in the HomeController.

  1. Add a new action called Detail and add a [Sitemap] attribute with the QueryKey = “GetAllProducts”, like this:
    [Sitemap(QueryKey = "GetAllProducts")]
    public ActionResult Detail()
    {
    return View();
    }
  2. Add a new static method to the HomeController called GetAllProducts:
    private static Url[] GetAllProducts()
    {
    List<Url> result = new List<Url>();
    string baseUrl = @"http://localhost:58496/Home/Detail";

    result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 1) });
    result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 2) });
    result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 3) });
    result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 4) });
    result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 5) });
    result.Add(new Url() { Location = String.Format("{0}/{1}", baseUrl, 6) });

    return result.ToArray();
    }
  3. Update the Sitemap action to the following:
    public ContentResult SiteMap()
    {
    Dictionary<string, Func<Url[]>> queries = new Dictionary<string, Func<Url[]>>();
    queries.Add("GetAllProducts", GetAllProducts);

    return Content(SitemapFactory.Create(queries), "text/xml", System.Text.Encoding.UTF8);
    }
  4. Press F5 to start debugging, then navigate to the Sitemap action and you should see something similar to the following:
    <?xml version="1.0" encoding="utf-8" ?> 
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
    <loc>http://localhost:58496/home/Index</loc>
    </url>
    <url>
    <changefreq>weekly</changefreq>
    <priority>0.7</priority>
    <loc>http://localhost:58496/home/About</loc>
    </url>
    <url>
    <loc>http://localhost:58496/home/Detail</loc>
    </url>
    <url>
    <loc>http://localhost:58496/Home/Detail/1</loc>
    </url>
    <url>
    <loc>http://localhost:58496/Home/Detail/2</loc>
    </url>
    <url>
    <loc>http://localhost:58496/Home/Detail/3</loc>
    </url>
    <url>
    <loc>http://localhost:58496/Home/Detail/4</loc>
    </url>
    <url>
    <loc>http://localhost:58496/Home/Detail/5</loc>
    </url>
    <url>
    <loc>http://localhost:58496/Home/Detail/6</loc>
    </url>
    </urlset>

Let’s briefly walk through this. We created a new action and decorated it with a [Sitemap] attribute. The sitemap attribute is looking for a query with a key of “GetAllProducts” to be passed to the Create method of the SitemapFactory.

We then added the GetAllProducts static method to the HomeController. This function had a return type of Url[] since that is the only return type currently accepted by SitemapFactory for any delegates passed to it. The GetAllProducts method contained some business logic to create an array of Url. In this case we were simply creating instances of the Url class and adding it to the List, however, this method could contain some complicated logic which retrieves the Urls via LINQ to SQL, Entity Framework, WCF, or practically any other means.

Finally we updated our Sitemap action to be aware of the queries we were sending to it. Notice how we instantiated the queries dictionary with Func<Url[]> as the value of the dictionary, which is exactly what the SitemapFactory is expecting - a function with a return type of Url[].

Wrap Up

Hopefully this is a useful walkthrough of how to use the Mvc Xml Sitemap framework. Please feel free to leave comments either here or on Codeplex.

author: Ryan Graham | posted @ Saturday, August 01, 2009 2:07 PM | Feedback (58)

I’m back from being MIA


So I’m back from being MIA for oh… about 4 months. :-)

I had a lot going on in my personal life and just couldn’t find the time or motivation to write any posts (there I go making excuses again). Part of the reason I’ve not had time to write is that I’ve been spending a lot of time working with Silverlight, specifically SL3 and I’m loving it. Silverlight is such a powerful development platform and the tooling support is great – minus the designer surface no longer working in VS2008 for SL3. The only good thing that’s come from that is I’ve been forced to use Blend 3 + SketchFlow and I’ve fallen in love with it. I introduced SketchFlow at work for doing rapid prototyping and everyone loves it – designers, developers, and stakeholders. If any of you Microsofties out there read this, I wouldn’t mind if a copy of Expression Studio 3 mysteriously showed up on my doorstep. ;-)

Anyway, much has happened since I posted last:

  • Silverlight 3 RTM
  • Expression Blend + SketchFlow RTM
  • Azure July 2009 CTP
  • RIA Services July 2009 CTP
  • ASP.NET MVC 2 Preview 1

That just means we have a lot of new goodies to play with and there are now plenty of topics for me to write about. :-) I’ll be kicking off my next round of posts with one about the ASP.NET MVC XML Sitemap project I just launched on Codeplex. If I can keep my eyes open long enough I might just hammer it out tonight.

author: Ryan Graham | posted @ Saturday, August 01, 2009 12:32 AM | Feedback (8)

Simple Silverlight Security Preview


I love the [Authorize()] attribute in ASP.NET MVC for 2 main reasons:

  1. It’s easy to use.
  2. It “feels” lightweight.

I wish Silverlight/Prism had a similar security mechanism – but it doesn’t (at least not that I’ve found). Being new to the Silverlight scene I figured I’d take a stab at creating a reusable security mechanism that could be used in a stock Silverlight or Silverlight/Prism application.

The only requirements are that it must:

  1. Be easy to use.
  2. Feel lightweight.

Having said that, I wanted to post a code snippet of a Silverlight security solution that I feel captures the 2 requirements mentioned above.

<UserControl x:Class="Sandbox.Web.Shell.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:clr="clr-namespace:System;assembly=mscorlib"
    xmlns:local="clr-namespace:Sandbox.Web.Modules.Security.Converters;assembly=Sandbox.Web.Modules.Security">
    <UserControl.Resources>
        <local:AuthorizedEnabledConverter x:Key="ButtonEnabled" />
        <local:AuthorizedVisibleConverter x:Key="TextBlockVisible" />
        <clr:String x:Key="Authorization">Users=John,Jane;Roles=Administrator</clr:String>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Button Grid.Column="0" Grid.Row="0" Content="Click Me If You Can" IsEnabled="{Binding Source={StaticResource Authorization}, Converter={StaticResource ButtonEnabled}}" Margin="5" />
        <TextBlock Grid.Column="1" Grid.Row="0" Text="Hello World" Visibility="{Binding Source={StaticResource Authorization}, Converter={StaticResource TextBlockVisible}}" Margin="5" />
    </Grid>
</UserControl>

The important pieces of the XAML code above are the AuthorizedEnabledConverter and AuthorizedVisibleConverter converters declared in the UserControl.Resources section and how I used them in the Binding of the IsEnabled and Visibility properties of the Button and TextBlock. For the purposes of this post, I simply declared a static string resource with hardcoded users and roles, but this could just as easily be passed in as a property of a ViewModel or handled in a Presenter or Controller. The missing part of the puzzle (and the part I’m currently working on) is how I resolve the current user’s Username and roles within the converters.

So what does everyone think? Is this a viable security mechanism for Silverlight? Does it stay true to the requirements of being easy to use and lightweight?

author: Ryan Graham | posted @ Sunday, April 05, 2009 9:55 PM | Feedback (13)

Part 2: Configuring the Prism.Framework.Web.Shell Project


In Part 1 we laid the foundation of our Silverlight/Prism solution by setting up the following projects: Prism.Framework.Api, Prism.Framework.Web.Shell, Prism.Framework.Web.

In Part 2 we’re going to pick up where we left off and configure our Prism.Framework.Web.Shell project for use with the Prism framework. Before we begin, let’s quickly review the prerequisites.

Prerequisites

  • You’re familiar with the Prism guidance concepts and understand why you would use it to build a Silverlight application. If you’re not familiar with Prism and it’s concepts, stop right now and read the documentation on MSDN. I’ll wait for you, I promise. :)
  • You’re an experienced C#/VB software developer.
  • You have some exposure (or a strong interest in learning) software design patterns such as Inversion of Control (IoC), Dependency Injection (DI), Model-View-Presenter (MVP), Model-View-ViewModel (MVVM).
  • You have Visual Studio 2008 SP1 installed on your system.
  • You have Silverlight Tools for Visual Studio 2008 SP1 installed on your system.
  • You have some exposure working with Silverlight 2 (doesn’t need to be much – as long as you’re familiar with the structure of a Silverlight 2 project, XAML, and code behind).

Compiling The Prism Libraries

We have one more house keeping chore we need to take care of before we start with overhauling the stock Silverlight project with Prism – compiling the Prism source code. Again, this is a relatively trivial task.

NOTE: If you are running a 64-bit OS, substitute C:\Program Files (x86)\ for C:\Program Files\ in all file paths.

  1. Fire up VS2008 and open the following solution (assuming you placed the Prism folder in the location suggested in Part 1): C:\Program Files\Microsoft SDKs\Prism V2\CAL\CompositeApplicationLibrary.sln.
  2. Right-Click on the CompositeApplicationLibrary solution –> Rebuild Solution

    image
  3. Change the Solution Configurations from Debug to Release and then Right-Click on the CompositeApplicationLibrary solution –> Rebuild Solution (this is just to rebuild the assemblies under Release configuration so they’re ready for when we need them).

    image 
  4. Now open up Windows Explorer and navigate to the following folder: C:\Program Files\Microsoft SDKs\Prism V2\.
  5. Create a new folder and rename it to: Bin
  6. Create two folders beneath the Bin folder: Debug, Release.
  7. Now navigate to: C:\Program Files\Microsoft SDKs\Prism V2\CAL\Silverlight\Composite.UnityExtensions\Bin\Debug
  8. Copy the following files to: C:\Program Files\Microsoft SDKs\Prism V2\Bin\Debug\
    • Microsoft.Practices.Composite.dll
    • Microsoft.Practices.Composite.Presentation.dll
    • Microsoft.Practices.Composite.UnityExtensions.dll
    • Microsoft.Practices.ServiceLocation.dll
    • Microsoft.Practices.Unity.dll
    • Microsoft.Practices.Composite.pdb
    • Microsoft.Practices.Composite.Presentation.pdb
    • Microsoft.Practices.Composite.UnityExtensions.pdb
    • Microsoft.Practices.Composite.xml
    • Microsoft.Practices.Composite.Presentation.xml
    • Microsoft.Practices.Composite.UnityExtensions.xml
  9. Now navigate to: C:\Program Files\Microsoft SDKs\Prism V2\LIB\Desktop\CommonServiceLocation
  10. Copy the following file to: C:\Program Files\Microsoft SDKs\Prism V2\Bin\Debug\
    • Microsoft.Practices.ServiceLocation.XML
  11. Now navigate to: C:\Program Files\Microsoft SDKs\Prism V2\LIB\Desktop\Unity
  12. Copy the following file to: C:\Program Files\Microsoft SDKs\Prism V2\Bin\Debug\
    • Microsoft.Practices.Unity.xml
  13. Repeat the steps above, this time getting the files from the Release folder instead of Debug and copy them into: C:\Program Files\Microsoft SDKs\Prism V2\Bin\Release\

Configuring the Prism.Framework.Web.Shell project for Prism

Now that we have all the house keeping tasks out of the way, we can get into the good stuff – adding Prism to our solution. I learn best by doing first, understanding second, so I’m going to take that approach here. I’ll do my best to walk you through with screen shots and code snippets. Sound good?

Add New References and Code Files to the Prism.Framework.Web.Shell Project

  1. If you don’t already have the Prism.Framework solution opened in Visual Studio, go ahead and do that.
  2. Right-Click on the Prism.Framework.Web.Shell project and click Add Reference…

    image
  3. Click on the Browse tab, then browse to the folder: C:\Program Files\Microsoft SDKs\Prism V2\Bin\Debug\
  4. Select the following files:
    • Microsoft.Practices.Composite.dll
    • Microsoft.Practices.Composite.Presentation.dll
    • Microsoft.Practices.Composite.UnityExtensions.dll
    • Microsoft.Practices.ServiceLocation.dll
    • Microsoft.Practices.Unity.dll
  5. Click OK
  6. Add a new interface file named IShellView.
  7. Add a new class file named Bootstrapper.

Modifying Our New Files

  1. Add the following method signature to the IShellView interface:

    • void ShowView()

  2. Add the following using statements to the Bootstrapper.cs file:
    • Microsoft.Practices.Composite.Modularity
    • Microsoft.Practices.UnityExtensions
  3. Update the Bootstrapper’s signature to inherit from UnityBootrapper and then implement the abstract class.

    image 
  4. Now override the following protected methods:
    • ConfigureContainer
    • GetModuleCatalog
  5. Change the GetModuleCatalog method to the following:
    protected override IModuleCatalog GetModuleCatalog()
    {
         ModuleCatalog catalog = new ModuleCatalog();
         return catalog;
    }
  6. Change the ConfigureContainer method to the following:
  7. protected override void ConfigureContainer()
    {
        Container.RegisterType<IShellView, Shell>();
        base.ConfigureContainer();
    }
  8. Change the CreateShell method to the following:
  9. protected override DependencyObject CreateShell()
    {
        IShellView view = Container.Resolve<IShellView>();
        view.ShowShell();
    
        return view as DependencyObject;
    }

Modify Page.xaml

  1. Right-Click on Page.xaml –> Rename
  2. Rename Page.xaml to Shell.xaml

    image
  3. Right-Click on Shell.xaml –> View Code
  4. Rename the class from Page to Shell

    image 
  5. While you have the public partial class Shell open, implement the IShellView interface.
  6. Change the ShowShell method to the following:
    public void ShowShell()
    {
        Application.Current.RootVisual = this;
    }
     
  7. Shell.cs should now look like:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    
    namespace Prism.Framework.Web.Shell
    {
        public partial class Shell : UserControl, IShellView
        {
            public Shell()
            {
                InitializeComponent();
            }
    
            #region IShellView Members
    
            public void ShowShell()
            {
                Application.Current.RootVisual = this;
            }
    
            #endregion
        }
    }
  8.  
  9. Add the following namespace declaration to the root UserControl element in Shell.xaml:
  10. xmlns:regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
  11. Add a TextBlock element to root UserControl element in Shell.xaml and give it an arbitrary Text value (for instance, Hello World!). This is just to make sure our Shell view is loading when we run the application.
  12. Add the following ItemsControl element to the root UserControl element in Shell.xaml beneath the previously added TextBlock element:
<ItemsControl x:Name="MainRegion" regions:RegionManager.RegionName="MainRegion" />


Shell.xaml should now look like:

<UserControl x:Class="Prism.Framework.Web.Shell.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <TextBlock Text="Hello World!" />
        <ItemsControl x:Name="MainRegion" regions:RegionManager.RegionName="MainRegion" />
    </Grid>
</UserControl>

Modify App.xaml.cs

  1. Right-Click on App.xaml –> View Code
  2. Change the Application_Startup method to the following:
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        Bootstrapper bootstrapper = new Bootstrapper();
        bootstrapper.Run();
    }
     
  3. Press F5 to run the application, you should see the following:

    image

What Did We Just Do? And Why The Heck Did We Do It?

Alright, we just did quite a bit of work so let’s break it down piece by piece – I’ll skip over adding the Prism assemblies to the Shell project because that’s pretty self explanatory.

Immediately after adding the references to the Prism assemblies we added 2 new files to our project: IShellView.cs and Bootstrapper.cs. We’ll cover IShellView and the concept of using interfaces for views in a future post covering design patterns, so for now we’ll just say that creating and implementing the IShellView interface helps make our solution more testable.

The really important bits here are in Bootstrapper.cs, which is essentially the backbone of our Prism solution. In Bootstrapper.cs we overrode the CreateShell, ConfigureContainer, and GetModuleCatalog methods of the UnityContainer class from which we inherited – let’s take a closer look.

Modularity is one of the core design concepts of the Prism framework and a composite application. In the GetModuleCatalog method we’re essentially setting up the capability for our application to specify which modules will be used. However, as of right now all we’re doing is instantiating and returning an empty ModuleCatalog instance. In the next post we’ll be adding a module to the solution and will therefore need to add it to the ModuleCatalog - which we’ll do via static code.

Let’s now shift our focus to the ConfigureContainer method. In this method we’re registering components with the dependency injection container for retrieval at a later time. The IShellView interface is specified as the type that will be requested, and the Shell class is specified as the type that will actually be returned. At first glance this may not seem like a big deal, but if you think about it, it’s actually quite important. Since we’re using interfaces to request types, we’re not tied to any specific implementation and can therefore swap out functionality as needed. The interfaces will also come in handy when it’s time to write automated unit tests – therefore making our application easier to maintain and hopefully producing a higher quality product.

In the CreateShell method we resolved the Shell class by requesting an IShellView from the dependency injection container. Once we’ve resolved the Shell, we call the ShowShell method and then return the Shell as a DependencyObject. There’s nothing too magical happening in this method, but I’d like to note that creating the Shell in the bootstrapper class aids in testability since we can mock the Shell in a unit testing framework.

Moving along to the Shell, the first thing we did was rename Page.xaml to Shell.xaml. This is merely for convention and we could have just as easily left Page.xaml alone. We then implemented the ShowShell method from the IShellView interface which sets the Application’s RootVisual equal to the instance of the Shell. In the Shell.xaml file we then added the regions namespace so we could add the ItemsControl which is the container control for our MainRegion region. Of course, we don’t have any views to inject into the MainRegion right now - but we’ll be adding one in the next post.

Finally, we modified the Application_Startup method of the App class to instantiate an instance of our bootstrapper class and call it’s Run method. The Run method essentially starts the process of configuring the container, configuring the region mappings, creating the shell, and initializing the modules. These activities directly relate to the methods we overrode when we created the Bootstrapper class which is precisely why we needed to override the default implementation of those methods. One last thing to point out. If you have a keen eye, you probably noticed the code we replaced within the Application_Startup method is nearly identical to the code we placed within the ShowShell method of the Shell class. By doing this we’re simply removing the responsibility of displaying the Shell as the application’s visual root from the App class and placing it in the hands of the Shell class which is more aligned with using the bootstrapper process.

This was a long but essential post and we’re finally ready to begin creating modules and add “real” functionality to our app. And that’s exactly where we’ll pick up in Part 3.

author: Ryan Graham | posted @ Sunday, April 05, 2009 8:51 PM | Feedback (6)

Part 1: Setting up our Silverlight/Prism Solution


As I mentioned in my very first blog post, I intend on writing a series of posts about using Silverlight with Prism – something similar to The Application Corner with Tim Heuer. In order to get a jump start on the series, I thought it’d be a good idea to get the “setup” post done and out of the way. So without further ado…

Prerequisites

In order to follow along with this post and series, I assume the following:

  • You’re familiar with the Prism guidance concepts and understand why you would use it to build a Silverlight application. If you’re not familiar with Prism and it’s concepts, stop right now and read the documentation on MSDN. I’ll wait for you, I promise. :)
  • You’re an experienced C#/VB software developer.
  • You have some exposure (or a strong interest in learning) software design patterns such as Inversion of Control (IoC), Dependency Injection (DI), Model-View-Presenter (MVP), Model-View-ViewModel (MVVM).
  • You have Visual Studio 2008 SP1 installed on your system.
  • You have Silverlight Tools for Visual Studio 2008 SP1 installed on your system.
  • You have some exposure working with Silverlight 2 (doesn’t need to be much – as long as you’re familiar with the structure of a Silverlight 2 project, XAML, and code behind).

Ok, now that I have the obligatory prereqs out of the way, let’s get started.

Setting up our Silverlight/Prism Solution

  1. For starters we need to install Prism on our local machine – which is relatively trivial:
    1. Download prism from this page. NOTE: You may also want to download the documentation - I prefer to use the online docs at: msdn.microsoft.com.
    2. Run the self extracting file and extract the files to a folder named Prism V2
    3. Move the Prism V2 folder extracted the files to C:\Program Files\Microsoft SDKs\ (or C:\Program Files (x86)\Microsoft SDKs\ if you’re running a 64-bit OS).
      NOTE: You don’t have to move the Prism V2 folder to the Microsoft SDKs folder, I just like to keep all my SDK stuff located in the a common place and the Microsoft SDKs folder makes sense.
  2. Alright, now fire up VS2008 and create a blank Visual Studio Solution named Prism.Framework.
    NOTE: The names you choose for your solutions and projects are arbitrary - it’ll just make things easier for you to use the names I do, especially if you’ll be following along with this blog series.

     image
  3. In the Solution Explorer, right-click on the Prism.Framework solution and click Add –> New Project… –> Visual C# –> Web –> WCF Service Application. Use Prism.Framework.Api as the project name. Click OK.

    image 
  4. In the Solution Explorer, right-click on the Prism.Framework solution and click Add –> New Project… –> Visual C# –> Silverlight –> Silverlight Application. Use Prism.Framework.Shell as the project name. Click OK.

    image
  5. In the Add Silverlight Application dialog window that pops up after step 4, choose Add a new ASP.NET Web project to the solution to host Silverlight, Project Type: ASP.NET Web Application Project, and use Prism.Framework.Web as the name.

    image

And that’s it! Let’s look at what we just did:

  1. Created a Prism.Framework solution to house all the projects related to this blog series.
  2. Created the Prism.Framework.Api WCF project which will serve as the means of communicating with backend systems. This will be deployed to the web server as: http://api.yourdomain.com.
  3. Created the Prism.Framework.Web.Shell project. This project will serve as the application shell into which we’ll load our application’s various modules.
  4. Created the Prism.Framework.Web project to host the Silverlight shell. This will eventually be deployed to the web server as: http://www.yourdomain.com.

Now you might be asking yourself, why can’t we just use the Prism.Framework.Web project to host both the Silverlight shell and WCF services projects? We could certainly do that, but by putting the WCF services in their own project, we decouple those services from the web site hosting the Silverlight shell, therefore making them logically available to other applications. Plus, now we have a reason to implement a Silverlight clientaccesspolicy.xml file in a future post. :)

In the next post we’ll configure the Prism.Framework.Web.Shell project to use the Prism framework.

author: Ryan Graham | posted @ Sunday, March 22, 2009 7:11 PM | Feedback (20)

Subtext Sitemap for Google Sitemaps


This wasn’t in the documentation anywhere (at least I couldn’t find it), but if you want to add your Subtext powered blog to Google Sitemaps you need to reference the following URL: http://www.yourdomainhere.com/sitemap.ashx. So for my blog the URL is: http://www.rjygraham.com/sitemap.ashx.

Just a heads up to everyone.

author: Ryan Graham | posted @ Sunday, March 22, 2009 1:54 PM | Feedback (11)