Export (0) Print
Expand All

Walkthrough: Using Authentication Service with Silverlight Business Application

WCF RIA Services

[WCF RIA Services Version 1 Service Pack 2 is compatible with either .NET framework 4 or .NET Framework 4.5, and with either Silverlight 4 or Silverlight 5.]

The Silverlight Business Application template creates a solution that automatically enables authentication (with Forms Authentication for the authentication mode), roles, and profiles. The solution includes data forms for logging in existing users and registering new users. You can use these features without writing any additional code. You can customize the solution by defining roles and profile properties.

In this walkthrough, you will learn how to use authentication, roles, and profiles in a Silverlight Business Application. You will restrict access to certain domain operations based on the user's credentials and customize the user interface based on user preferences. You will use the ASP.NET Web Site Administration Tool for managing roles and users in the site.

This and the other walkthroughs presented in the RIA Services documentation require several prerequisite programs, such as Visual Studio 2010 and the Silverlight Developer Runtime and SDK, be installed and configured properly, in addition to WCF RIA Services and the WCF RIA Services Toolkit. They also require installing and configuring SQL Server 2008 R2 Express with Advanced Services and installing the AdventureWorks OLTP and LT database.

Detailed instructions for the satisfaction of each of these prerequisites are provided by the topics within the Prerequisites for WCF RIA Services node. Follow the instructions provided there before proceeding with this walkthrough to ensure that you encounter as few problems as possible when working through this RIA Services walkthroughs.

You can use the provided features in a Silverlight Business Application to quickly implement authentication. In the following section, you will use the ASP.NET Configuration tool to create a user and role, and then log in as that user. You will register a new user through the registration form that is provided in the Silverlight Business Application.

To create site, roles and users

  1. In Visual Studio 2010, select File, New, and then Project.

    The New Project dialog box appears.

  2. Select the Silverlight project type.

  3. Select the Silverlight Business Application template and name the application ExampleBusinessApplication.

    RIA_ServicesCreateBizApp
  4. Click OK.

    Notice the project structure that is created. The Silverlight client project includes Silverlight pages in the Views folder. These pages enable logging in users and registering new users.

  5. To open the ASP.NET Web Site Administration Tool, first select the server project (ExampleBusinessApplication.Web) in Solution Explorer. open the ASP.NET Configuration tool.

  6. On the Project menu, select ASP.NET Configuration.

    If you do not see the ASP.NET Configuration option in the Project menu, it may be because you have selected the client project.

    RIA_OpenAdminTool
  7. Select the Security tab in the ASP.NET Web Site Administration Tool.

    RIA_WebAdminSecurity
  8. In the Roles section, click the Create or Mange roles link.

  9. Add a new role named Managers and select the Add Role button.

    WebAdmin_CreateRole
  10. In the lower-right corner, click the Back button.

  11. In the Users section, click the Create user link.

  12. Create a new user with the following values and select the Managers role check box.

    User Name: CustomerManager

    Password: P@ssword

    E-mail: someone@example.com

    Security Question: Favorite color?

    Security Answer: Blue

    Managers role: selected

    WebAdmin_CreateUser
  13. Click the Create User button.

  14. Close the ASP.NET Web Site Administration Tool.

  15. Run the solution.

    The Home page for the application appears in a Web browser.

  16. In the upper-right corner of the page, click the login link.

    A Login dialog box appears.

  17. Enter CustomerManager for the user name and P@ssword for the password, and click OK button.

    RIA_LoginManager

    You are now logged in as that user. Notice the text "Welcome CustomerManager" in the upper-right corner.

  18. Click the logout link.

    You are no longer logged in as CustomerManager. In the following steps, you will create a new user through the registration form.

  19. Click login link again.

  20. In the Login dialog box, click the Register now link.

    The registration form is now displayed.

  21. Fill out the registration form to create a new user account. Use the following values for the new user.

    Username: SalesUser

    Friendly name: SalesUser

    Email: someone@example.com

    Password: P@ssword

    Security Question: What was the color of your first car?

    Security Answer: Green

    RIA_RegisterUser
  22. Click OK to create the new user.

    Notice that you are now logged in as SalesUser.

  23. Close the browser.

  24. Open the ASP.NET Web Site Administration Tool again, and click the Security tab.

    Notice that 2 users now exist in the site and 2 roles exist even though you have only created one role.

  25. Click Create or Manage roles and notice the Managers role and the Registered Users.

    The Registered Users role was automatically created by the Business Application template.

    RIA_ManageRoles
  26. For Registered Users, click the Manage link.

    Notice that the user named SalesUser that you added through the application is in the Registered Users role.

  27. Close the ASP.NET Web Site Administration Tool.

You restrict access to a domain operation by applying either the RequiresAuthenticationAttribute attribute or the RequiresRoleAttribute attribute to the domain operation. Domain operations without an attribute are available to all users. Applying an attribute to domain operation does not prevent the user from calling the domain operation; however, users without the required credentials will receive an exception.

Restrict Displayed Data by Roles

  1. In Solution Explorer, right-click the App_Data folder in the server project, select Add and then Existing Item.

  2. In the Add Existing Item dialog box, add the AdventureWorksLT sample database.

  3. In the server project, add a new item and select the ADO.NET Entity Data Model template from the Data templates.

  4. Name the model AdventureWorksModel.edmx and click Add.

    The Entity Data Model Wizard appears.

  5. Select the Generate from database option and then click Next.

  6. Select the AdventureWorksLT database and then click Next.

  7. From the list of database objects, select the Customer, Product, and SalesOrderHeader tables, and then click Finish.

    The entity data model appears in the designer.

  8. Build the solution.

  9. In the server project, add a new item and select the Domain Service Class template from the Web templates.

  10. Name the domain service AdventureWorksDomainService and then click Add.

  11. In the Add New Domain Service Class dialog box, select the Customer, Product, and SalesOrderHeader entities.

    RIA_CreateDSForAuth
  12. Click OK to finish creating the domain service.

  13. In the AdventureWorksDomainService class file, add the RequiresAuthenticationAttribute attribute to GetSalesOrderHeader method.

    [RequiresAuthentication()]
    public IQueryable<SalesOrderHeader> GetSalesOrderHeaders()
    {
        return this.ObjectContext.SalesOrderHeaders;
    }
    
    
    
  14. Add the RequiresRoleAttribute attribute to the GetCustomers method, and set the name of the required role to "Managers".

    [RequiresRole("Managers")]
    public IQueryable<Customer> GetCustomers()
    {
        return this.ObjectContext.Customers;
    }
    
    
    

    The GetProducts domain operation is available to any user, GetSalesOrderHeaders is available to authenticated users, and GetCustomers is available to only users in the Managers role.

    The following shows the complete domain service.

    [EnableClientAccess()]
    public class AdventureWorksDomainService : LinqToEntitiesDomainService<AdventureWorksLT_DataEntities>
    {
        [RequiresRole("Managers")]
        public IQueryable<Customer> GetCustomers()
        {
            return this.ObjectContext.Customers;
        }
    
        public IQueryable<Product> GetProducts()
        {
            return this.ObjectContext.Products;
        }
    
        [RequiresAuthentication()]
        public IQueryable<SalesOrderHeader> GetSalesOrderHeaders()
        {
            return this.ObjectContext.SalesOrderHeaders;
        }
    }
    
    
    

You define a profile property in the Web.config file. When you add the property to the User class on the server, the corresponding property is generated for the client project.

Add profile properties

  1. In the server project, open the Web.config file.

  2. In the <profile> element, add a profile property named DefaultRows. The property will contain the user's preference for the number of rows to display.

    The following shows the profile section of the Web.config file.

    <profile>
      <properties>
        <add name="FriendlyName" />
        <add type="System.Int32" defaultValue="10" name="DefaultRows"/>
      </properties>
    </profile>
    
    
    
  3. Save the Web.config file.

  4. In the server project, expand the Models folder.

  5. Open the User.cs or User.vb file, and add a property named DefaultRows.

    namespace ExampleBusinessApplication.Web
    {
        using System.Runtime.Serialization;
        using System.ServiceModel.DomainServices.Server.ApplicationServices;
    
        public partial class User : UserBase
        {
            public string FriendlyName { get; set; }
    
            public int DefaultRows { get; set; }
        }
    }
    
    
    

Before calling a domain operation with restricted permissions, you should check that the user has the required credentials; otherwise, an exception is thrown. In the following section, you will check the user's credentials and populate from one to three DataGrid controls based on the user's credentials. You will also retrieve the number of records based on a property in the user profile. A default value of 10 is used for users that are not authenticated. This section does not include a way for users to set the DefaultRows profile property, but you will add that in a later section.

Add a Silverlight Page for displaying data

  1. In the client project, add a new item to the Views folder.

  2. Select the Silverlight Page template and name the new page Reports.xaml.

  3. Open the MainPage.xaml file and add a link for the Reports page by adding the following XAML after the HyperlinkButton named Link2 that links to the About page.

    <Rectangle x:Name="Divider2" Style="{StaticResource DividerStyle}"/>
    
    <HyperlinkButton x:Name="Link3" Style="{StaticResource LinkStyle}" 
           NavigateUri="/Reports" TargetName="ContentFrame" Content="{Binding Path=ApplicationStrings.ReportsPageTitle, Source={StaticResource ResourceWrapper}}"/>
    
    
    
  4. In the Assets\Resources folder, open the ApplicationStrings.resx file.

  5. Add a new string resource named ReportsPageTitle with the value of Reports.

    RIA_AddReportResource
  6. Save and close the ApplicationStrings.resx file.

  7. Open the Reports.xaml file, and add the following XAML to the Grid element to match the formatting of the other pages in the site.

    <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}">
        <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}">
            <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"
                       Text="{Binding Path=ApplicationStrings.ReportsPageTitle, Source={StaticResource ResourceWrapper}}"/>
            <TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}"
                       Text="Display reports based on user permissions"/>
    
        </StackPanel>
    </ScrollViewer>
    
    
    
  8. Drag three DataGrid controls from the Toolbox to just before the end tag of the stack panel named ContentStackPanel.

    When you drag the DataGrid controls from the Toolbox, a reference to the System.Windows.Controls.Data assembly is added to the project and a prefix for the System.Windows.Controls namespace is added to the page.

  9. Name the DataGrid controls ProductsGrid, SalesOrdersGrid, and CustomersGrid.

  10. For each DataGrid control, set the Margin property to 5.

    The following example shows the complete Reports.xaml file.

    <navigation:Page x:Class="ExampleBusinessApplication.Views.Reports"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
               xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
               mc:Ignorable="d"
               xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
               d:DesignWidth="640" d:DesignHeight="480"
               Title="Reports Page" >
        <Grid x:Name="LayoutRoot">
            <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}">
                <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}">
                    <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"
                               Text="{Binding Path=ApplicationStrings.ReportsPageTitle, Source={StaticResource ResourceWrapper}}"/>
                    <TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}"
                               Text="Display reports based on user permissions"/>
                    <data:DataGrid Name="ProductsGrid" Margin="5" />
                    <data:DataGrid Name="SalesOrdersGrid" Margin="5" />
                    <data:DataGrid Name="CustomersGrid" Margin="5" />
                </StackPanel>
            </ScrollViewer>
        </Grid>
    </navigation:Page>
    
    
    
  11. Open the Reports.xaml.cs or Reports.xaml.vb file.

  12. For C#, add using statements for the System.ServiceModel.DomainServices.Client, System.ServiceModel.DomainServices.Client.ApplicationServices, and ExampleBusinessApplication.Web namespaces. For Visual Basic, add Imports statements for the System.ServiceModel.DomainServices.Client, System.ServiceModel.DomainServices.Client.ApplicationServices, System.Windows.Controls, and ExampleBusinessApplication.Web namespaces.

  13. Create an instance of the AdventureWorksDomainContext named context, and create a variable named numberOfRows that contains the number of rows to retrieve.

    private AdventureWorksDomainContext context = new AdventureWorksDomainContext();
    int numberOfRows = 10;
    
    
    
  14. Add a method named LoadRestrictedReports that calls the GetSalesOrderHeaderQuery method and the GetCustomersQuery method, if the user belongs to the Managers role, and populates the corresponding data grids with the results.

    If you call a domain operation when the user does not have the required credentials, the domain operation returns an exception. You can avoid this situation by checking the credentials before calling the domain operation.

    private void LoadRestrictedReports()
    {
        LoadOperation<SalesOrderHeader> loadSales = context.Load(context.GetSalesOrderHeadersQuery().Take(numberOfRows));
        SalesOrdersGrid.ItemsSource = loadSales.Entities;
        SalesOrdersGrid.Visibility = System.Windows.Visibility.Visible;
    
        if (WebContext.Current.User.IsInRole("Managers"))
        {
            LoadOperation<Customer> loadCustomers = context.Load(context.GetCustomersQuery().Take(numberOfRows));
            CustomersGrid.ItemsSource = loadCustomers.Entities;
            CustomersGrid.Visibility = System.Windows.Visibility.Visible;
        }
        else
        {
            CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
        }
    }
    
    
    
  15. Add a method named LoadReports that checks whether the user is authenticated and, if so, calls LoadRestrictedReports method. It also retrieves the profile property named DefaultRows and adds an event handler for the PropertyChanged event on the User object. Finally, it calls the GetProductsQuery method for all users.

    private void LoadReports()
    {
        if (WebContext.Current.User.IsAuthenticated)
        {
            numberOfRows = WebContext.Current.User.DefaultRows;
            WebContext.Current.User.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(User_PropertyChanged);
            LoadRestrictedReports();
        }
        else
        {
            CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
            SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
        }
    
        LoadOperation<Product> loadProducts = context.Load(context.GetProductsQuery().Take(numberOfRows));
        ProductsGrid.ItemsSource = loadProducts.Entities;
    }
    
    
    
  16. Add an event handler for the PropertyChanged event that calls LoadReports if the property DefaultRows has changed.

    void User_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "DefaultRows")
        {
            LoadReports();
        }
    }
    
    
    
  17. Add event handlers for the LoggedIn and LoggedOut events that either load or hide data based on the change in the user authentication credentials.

    void Authentication_LoggedIn(object sender, AuthenticationEventArgs e)
    {
        LoadReports();
    }
    
    void Authentication_LoggedOut(object sender, AuthenticationEventArgs e)
    {
        CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
        SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
    }
    
    
    
  18. Add the following code to the constructor. This code loads the handlers and calls LoadReports.

    public Reports()
    {
        InitializeComponent();
    
        this.Title = ApplicationStrings.ReportsPageTitle;
    
        WebContext.Current.Authentication.LoggedIn += new System.EventHandler<AuthenticationEventArgs>(Authentication_LoggedIn);
        WebContext.Current.Authentication.LoggedOut += new System.EventHandler<AuthenticationEventArgs>(Authentication_LoggedOut);
    
        LoadReports();
    }
    
    
    

    The following shows the complete code file.

    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;
    using System.Windows.Navigation;
    using System.ServiceModel.DomainServices.Client;
    using System.ServiceModel.DomainServices.Client.ApplicationServices;
    using ExampleBusinessApplication.Web;
    
    namespace ExampleBusinessApplication.Views
    {
        public partial class Reports : Page
        {
            private AdventureWorksDomainContext context = new AdventureWorksDomainContext();
            int numberOfRows = 10;
    
            public Reports()
            {
                InitializeComponent();
    
                this.Title = ApplicationStrings.ReportsPageTitle;
    
                WebContext.Current.Authentication.LoggedIn += new System.EventHandler<AuthenticationEventArgs>(Authentication_LoggedIn);
                WebContext.Current.Authentication.LoggedOut += new System.EventHandler<AuthenticationEventArgs>(Authentication_LoggedOut);
    
                LoadReports();
            }
    
            private void LoadReports()
            {
                if (WebContext.Current.User.IsAuthenticated)
                {
                    numberOfRows = WebContext.Current.User.DefaultRows;
                    WebContext.Current.User.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(User_PropertyChanged);
                    LoadRestrictedReports();
                }
                else
                {
                    CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
                    SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
                }
    
                LoadOperation<Product> loadProducts = context.Load(context.GetProductsQuery().Take(numberOfRows));
                ProductsGrid.ItemsSource = loadProducts.Entities;
            }
    
            private void LoadRestrictedReports()
            {
                LoadOperation<SalesOrderHeader> loadSales = context.Load(context.GetSalesOrderHeadersQuery().Take(numberOfRows));
                SalesOrdersGrid.ItemsSource = loadSales.Entities;
                SalesOrdersGrid.Visibility = System.Windows.Visibility.Visible;
    
                if (WebContext.Current.User.IsInRole("Managers"))
                {
                    LoadOperation<Customer> loadCustomers = context.Load(context.GetCustomersQuery().Take(numberOfRows));
                    CustomersGrid.ItemsSource = loadCustomers.Entities;
                    CustomersGrid.Visibility = System.Windows.Visibility.Visible;
                }
                else
                {
                    CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
                }
            }
    
            void Authentication_LoggedIn(object sender, AuthenticationEventArgs e)
            {
                LoadReports();
            }
    
            void Authentication_LoggedOut(object sender, AuthenticationEventArgs e)
            {
                CustomersGrid.Visibility = System.Windows.Visibility.Collapsed;
                SalesOrdersGrid.Visibility = System.Windows.Visibility.Collapsed;
            }
    
            void User_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "DefaultRows")
                {
                    LoadReports();
                }
            }
        }
    }
    
    
    
  19. Run the solution.

  20. Click the Reports link.

    Notice that when you are not logged in, only the products table is displayed on the Reports page.

  21. Click the login link and log in as SalesUser.

    Notice that the tables for products and sales orders are displayed.

    RIA_DisplayReports
  22. Log out and log in as CustomerManager.

    Notice that the tables for products, sales orders, and customers are displayed.

  23. Close the Web browser.

You can allow users to edit the DefaultRows profile property by adding child window. When the value is changed, you call the SaveUser method to save the value in the data source. You retrieve the current value through the properties on the User object of the current WebContext instance.

Add a window for setting profile property

  1. In the client project, add a new item to the Views folder.

  2. Select the Silverlight Child Window template and name the child window ProfileWindow.xaml.

    Add Child Window
  3. Click the Add button.

  4. In the ProfileWindow.xaml file, add the following XAML after the Grid.RowDefinitions element to include a ComboBox for selecting the number of rows to display in the reports.

    <StackPanel Orientation="Horizontal" Grid.Row="0">
        <TextBlock Text="Number of rows to display for reports: "></TextBlock>
        <ComboBox x:Name="defaultRows" Height="20" VerticalAlignment="Top">
            <ComboBoxItem Content="1"></ComboBoxItem>
            <ComboBoxItem Content="2"></ComboBoxItem>
            <ComboBoxItem Content="3"></ComboBoxItem>
            <ComboBoxItem Content="4"></ComboBoxItem>
            <ComboBoxItem Content="5"></ComboBoxItem>
            <ComboBoxItem Content="6"></ComboBoxItem>
            <ComboBoxItem Content="7"></ComboBoxItem>
            <ComboBoxItem Content="8"></ComboBoxItem>
            <ComboBoxItem Content="9"></ComboBoxItem>
            <ComboBoxItem Content="10"></ComboBoxItem>
            <ComboBoxItem Content="15"></ComboBoxItem>
            <ComboBoxItem Content="20"></ComboBoxItem>
        </ComboBox>
    </StackPanel>
    
    
    
  5. Set the Title property on the ChildWindow to Select Preferences.

  6. In the ProfileWindow.xaml.cs or ProfileWindow.xaml.vb file, add the following code to retrieve and set the profile property.

    public partial class ProfileWindow : ChildWindow
    {
        public ProfileWindow()
        {
            InitializeComponent();
    
            string userDefaultRows = WebContext.Current.User.DefaultRows.ToString();
            foreach (ComboBoxItem cbi in defaultRows.Items)
            {
                if (cbi.Content.ToString() == userDefaultRows)
                {
                    defaultRows.SelectedItem = cbi;
                    break;
                }
            }
        }
    
        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            int newSelection = int.Parse(defaultRows.SelectionBoxItem.ToString());
            if (newSelection != WebContext.Current.User.DefaultRows)
            {
                WebContext.Current.User.DefaultRows = newSelection;
                WebContext.Current.Authentication.SaveUser(true);
            }
            this.DialogResult = true;
        }
    
        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }
    }
    
    
    
  7. For Visual Basic, add Imports statements for the System.Windows.Controls, and System.Windows namespaces.

  8. Expand the Views\Login folder, and open the LoginStatus.xaml file.

  9. To add a settings link to the profile window, add the following XAML before the logout button.

    <Button x:Name="SettingsButton" Click="SettingsButton_Click" Content="settings" Style="{StaticResource LoginRegisterLinkStyle}" Margin="0,0,0,0"></Button>
    <TextBlock Text="  |  " Style="{StaticResource SpacerStyle}"/>
    
    
    
  10. In the LoginStatus.xaml.cs or LoginStatus.xaml.vb file, add the following click event handler for the settings link.

    private void SettingsButton_Click(object sender, RoutedEventArgs e)
    {
        ExampleBusinessApplication.Views.ProfileWindow settingsWindow = new ExampleBusinessApplication.Views.ProfileWindow();
        settingsWindow.Show();
    }
    
    
    
  11. Run the solution.

  12. Log in as either CustomerManager or SalesUser and notice the login status bar now includes a link for settings.

    RIA_NewLoginStatusBar
  13. Click the settings link and set the default number of rows to show for the reports.

    RIA_ShowProfileSettings
  14. Open the Reports page and notice that the DataGrid now contains the number of rows that you selected.

Show:
© 2014 Microsoft