다음을 통해 공유


Mix09: Building Amazing Business Applications with Silverlight 3

Today at Mix09, I finished my talk on building business applications with Silverlight 3.   The full video for the talk will be up shortly, but I wanted to go ahead and post the source code for the demo and a full demo walk through. 

Update: Check out the video of the full session

The demo requires (all 100% free and always free):

  1. VS2008 SP1 (and Sql Express 2008)
  2. Silverlight 3 Beta
  3. .NET RIA Services March '09 Preview

Also, download the full demo files and check out the running application.

There are a few must-haves for a business applications..  this demo walks through each of these. 

Business Application Must-Haves

    1. Professional Feel
    2. Rich Data Query
    3. Validating Data Update 
    4. Authentication  
    5. Support Different Views

Professional Feel

Business applications need to have a professional, but how many of us have a full time designer on our staff?  I’d guess very few.  That is why we invested in a great out of box experience.  The Silverlight Navigation Application project starts with  a clean, prescriptive application structure, a nice look and feel, and is very easy to customize, even for a developer.

File\New Project – Silverlight Navigation Application

image

Hit F5

image

This is a full frame Silverlight Application.  Notice the navigation links (home and about).

image

Notice the forward and back in the browser works…  

image

And there is a deep link, that navigates you back to exactly this point in the application.  You can cut and paste it into a blog entry, an email or an IM to your co-works and they will be taken to exactly the same point in the app.

image

… no mater what browser they are using. 

Now, even the best developers sometimes make errors in applications.  Links that are invalid or exceptions that get thrown.  The Navigation Application Template makes it super easy to deal with those.      Type in a bad URL and look at the experience (be sure to run in retail). 

image

Now, let’s go in and look a bit of customization. 

First, let’s add a new page. 

Right click on Views in the client project and Add New Item, Select Silverlight Page.

image

When the page opens, add some simple text..

 <TextBlock Text="Hello World!"></TextBlock>

In MainPage.xaml, add a new button following the same format as the ones that are there.

 <Button Click="NavButton_Click" 
         Tag="/Views/MyFirstPage.xaml" 
         Content="my first" 
         Style="{StaticResource PageLinkStyle}"/>

Now, while we are in there, let’s customize the error window template… Open Views\ErrorWindow.xaml.  You can change the format, log the error back to the server or customize the text on the error message the end user sees. 

Now, let’s update the branding for this site.  First in MainPage.xaml, change the name from “your.application.name” to something custom to your app. 

Finally, let’s go in and change the colors to match your companies branding.    While all the styling is there for you to customize, we made a few of the common properties easy to find and change even for a developer.

image

Fit F5 and see what we have….

image

image

As you can see, my color choices aren’t great, so it is good that we are shipping a whole library of app.xaml files for you to choice from.    If you just drag one of the light, clean ones..  hit F5..

image

2. Rich Data Query

Next up, let’s talk about data query.  Nearly every business application deals with data.   Let’s look at how to do this with .NET RIA Services March ‘09 Preview.   For starters, let’s build an Entity Framework model for our data.   For this walk through, we will start simply…

image

Now, how are we going to access this data from the SL client?  Well, traditionally many business applications have started out as 2-tier apps. That has a number of problems around scaling and flexibility… and more to the point it just doesn’t work with the Silverlight\web client architecture.   

image

So developers are thrust into the n-tier world.  .NET RIA Services make it very easy to create scalable, flexible n-tier services that builds on WCF and ADO.NET Data Services. 

image

These .NET RIA Services model your UI-tier application logic and encapsulate your access to varies data sources from traditional relational data to POCO (plain old CLR objects) to cloud services such as Azure, S3, etc via REST, etc.  One of the great thing about this is that you can move from an on premises Sql Server to an Azure hosted data services without having to change any of your UI logic. 

Let’s look at how easy it is to create these .NET RIA Services. 

Right click on the server project and select the new Domain Service class

image

In the wizard, select your data source.  Notice here we could have choosen to use a Linq2Sql class, a POCO class, etc, etc. 

image

In the NorthwindDomainService.cs class we have stubs for all the CRUD method for accessing your data.   You of course should go in and customize these for your domain.  For the next few steps we are going to use GetSuperEmployees(), so I have customized it a bit. 

         public IQueryable<SuperEmployee> GetSuperEmployees()
        {
            return this.Context.SuperEmployeeSet
                .Where(emp=>emp.Issues>100)
                .OrderBy(emp=>emp.EmployeeID);
        }

.csharpcode {
   BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
   BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
   MARGIN: 0em
}
.csharpcode .rem {
  COLOR: #008000
}
.csharpcode .kwrd {
  COLOR: #0000ff
}
.csharpcode .str {
   COLOR: #006080
}
.csharpcode .op {
    COLOR: #0000c0
}
.csharpcode .preproc {
   COLOR: #cc6633
}
.csharpcode .asp {
   BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
   COLOR: #800000
}
.csharpcode .attr {
  COLOR: #ff0000
}
.csharpcode .alt {
   BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
 COLOR: #606060
}

Now, let’s switch the client side.  Be sure to build the solution so you can access it from the client directly.  These project are linked because we selected the “ASP.NET enable” in the new project wizard. 

In HomePage.Xaml add

 <datagrid:DataGrid x:Name="dataGrid1" Height="500">
</datagrid:DataGrid>

And in the code behind add MyApp.Web… notice this is interesting as MyApp.Web is defined on the server…  you can now access the client proxy for the server DomainServices locally

image

    1:   var context = new NorthwindDomainContext();
    2:   dataGrid1.ItemsSource = context.SuperEmployees;
    3:   context.LoadSuperEmployees();

In line 1, we create our NorthwindDomainContext.. this is the client proxy for NorthwindDomainService.  Notice the naming convention here…

In line 2, we are databinding the grid to the SuperEmployees.. then in line 3 we are loading the SuperEmployees, that class the GetSuperEmployees() method we defined on the server.  Notice this is all async of course, but we didn’t have to deal with the complexities of the async world. 

image

The result!  We get all our entries back, but in the web world,don’t we want to do paging and server side sorting and filtering?  Let’s look at how to do that.

First, remove the code we just added to codebehind.

Then, to replace that, let’s add a DomainDataSource.

    1:  <dds:DomainDataSource x:Name="dds" 
    2:     LoadMethodName="LoadSuperEmployees"
    3:     AutoLoad="True"
    4:     LoadSize="20">
    5:     <dds:DomainDataSource.DomainContext>
    6:        <App:NorthwindDomainContext></App:NorthwindDomainContext>
    7:     </dds:DomainDataSource.DomainContext>
    8:   
    9:  </dds:DomainDataSource>

Notice in line 2, we are calling that LoadSuperEmployees method from the DomainContext specified in line 6.  

In line 4, notice we are setting the LoadSize to 20.. that means we are going to download data in batches of 20 at a time.

    1:              <activity:Activity IsActive="{Binding IsBusy, ElementName=dds}"
    2:                             VerticalAlignment="Top" HorizontalAlignment="Left"
    3:                             Width="900" Margin="10,5,10,0">
    4:                  <StackPanel Width="900">
    5:   
    6:                      <datagrid:DataGrid x:Name="dataGrid1" Height="300"
    7:                                 ItemsSource="{Binding Data, ElementName=dds}"/>
    8:                      
    9:                      <dataControls:DataPager PageSize="10" 
   10:                      Source="{Binding Data, ElementName=dds}" />
   11:                  
   12:                  </StackPanel>
   13:              </activity:Activity>

In line 6, there is a datagrid, that is bound  to the DDS.Data property (in line 7).  Then we add a DataPager in line 9, that is bound to the same datasource.  this gives us the paging UI.  Finally we wrap the whole thing in an ActivityControl to show progress.  Notice in line 9 we are setting the display to 10 records at a time. 

The cool thing is that the ActivityControl, the DataGrid and the DataPager can all be used with any datasource such as data from WCF service, REST service, etc. 

Hit F5, and you see.. 

image

Notice we are loading 20 records at a time, but showing only 10.  So advancing one page is client only, but advancing again we get back to the server and load the next 20.  Notice this all works well with sorting as well.   And the cool thing is where is the code to handle all of this?  Did i write in on the server or the client?  neither.  Just with the magic of linq, things compose nicely and it i just falls out. 

I can early add grouping..

              <dds:DomainDataSource.GroupDescriptors>
                <data:GroupDescriptor PropertyPath="Publishers" />
            </dds:DomainDataSource.GroupDescriptors>

image

Let’s add filtering… First add a label and a textbox..

             <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
                <TextBlock Text="Origin: "></TextBlock>
                <TextBox x:Name="originFilterBox" Width="75" Height="20"></TextBox>
            </StackPanel>

and then these filter box to our DomainDataSource….

                 <dds:DomainDataSource.FilterDescriptors>
                    <data:FilterDescriptorCollection>
                        <data:FilterDescriptor PropertyPath="Origin"
                                               Operator="StartsWith">
                            <data:ControlParameter PropertyName="Text" 
                                               RefreshEventName="TextChanged"
                                               ControlName="originFilterBox">
                            </data:ControlParameter>
                        </data:FilterDescriptor>
                    </data:FilterDescriptorCollection>
                </dds:DomainDataSource.FilterDescriptors>

When we hit F5, we get a filter box, and as we type in it we do a server side filtering of the results. 

image

Now, suppose we wanted to make that a autocomplete box rather an a simple text box.   The first thing we’d have to do is get all the options.  Notice we have to get those from the server (they client might not have them all).  To do this we add a method to our DomainService. 

     public class Origin
    {
        public Origin() { }
        [Key]
        public string Name { get; set; }
        public int Count { get; set; }
    }

and the method that returns the Origins…

        public IQueryable<Origin> GetOrigins()
        {
            var q = (from emp in Context.SuperEmployeeSet
                    select emp.Origin).Distinct()
                    .Select(name => new Origin {
                        Name = name,
                        Count = Context.SuperEmployeeSet.Count
                            (emp => emp.Origin.Trim() == name.Trim())
                    });
        q = q.Where(emp => emp.Name != null);
            return q;
        }

Now we need to add the autocomplete control from the Silverlight 3 SDK.  Replace the textbox with this:

 <input:AutoCompleteBox  x:Name="originFilterBox" Width="75" Height="30"
    ValueMemberBinding="{Binding Name}" 
    ItemTemplate="{StaticResource OriginsDataTemplate}" >
</input:AutoCompleteBox>                                

Then we just need to add a little bit of code behind to load it up.

             var context = dds.DomainContext as NorthwindDomainContext;
            originFilterBox.ItemsSource = context.Origins;
            context.LoadOrigins();

Hitting F5 gives us this…

image

Validating Data Update

Now – that was certainly some rich ways to view data, but business apps need to update data as well.  Let’s look at how to do that.   First replace all the xaml below the DDS with this… it gives us a nice master-details view.

    1:              <StackPanel Orientation="Horizontal" Margin="10,5,10,0" >
    2:                  <activity:Activity IsActive="{Binding IsBusy, ElementName=dds}">
    3:                  <StackPanel>
    4:                      <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
    5:                          <TextBlock Text="Origin: "></TextBlock>
    6:   
    7:                              <input:AutoCompleteBox  x:Name="originFilterBox" Width="75" Height="30"
    8:                                                      ValueMemberBinding="{Binding Name}" 
    9:                                                      ItemTemplate="{StaticResource OriginsDataTemplate}" >
   10:                              </input:AutoCompleteBox>                                
   11:                      </StackPanel>
   12:                      
   13:                      <datagrid:DataGrid x:Name="dataGrid1" Width="290" Height="410" 
   14:                                             HorizontalAlignment="Left" HorizontalScrollBarVisibility="Disabled"
   15:                                         ItemsSource="{Binding Data, ElementName=dds}"                                    
   16:                                         AutoGenerateColumns="False" >
   17:                          <datagrid:DataGrid.Columns>
   18:                              <datagrid:DataGridTextColumn Header="Name" Binding="{Binding Name}" />
   19:                              <datagrid:DataGridTextColumn Header="Employee ID"  Binding="{Binding EmployeeID}" />
   20:                              <datagrid:DataGridTextColumn Header="Origin"  Binding="{Binding Origin}" />
   21:                          </datagrid:DataGrid.Columns>
   22:                      </datagrid:DataGrid>
   23:                      
   24:                      <dataControls:DataPager PageSize="13" Width="290" 
   25:                                              HorizontalAlignment="Left"
   26:                                              Source="{Binding Data, ElementName=dds}" />
   27:                      
   28:                      <StackPanel Orientation="Horizontal">
   29:                          <Button Content="Submit" Width="100" Height="30"
   30:                              Margin="0,20,20,0" HorizontalAlignment="Left"
   31:                             >
   32:                          </Button>
   33:                      </StackPanel>
   34:                  </StackPanel>
   35:                  </activity:Activity>
   36:                  
   37:                  <StackPanel Margin="40,40,0,0" >
   38:                      <dataControls:DataForm x:Name="dataForm1" Height="375" Width="500"
   39:                                 AutoEdit="True" AutoCommit="True"
   40:                                 VerticalAlignment="Top"       
   41:                                 CommandButtonsVisibility="None"
   42:                                 Header="Product Details"
   43:                                 CurrentItem="{Binding ElementName=dataGrid1, Path=SelectedItem}">
   44:                      </dataControls:DataForm>
   45:   
   46:                     
   47:                  </StackPanel>
   48:              </StackPanel>
   49:          </StackPanel>
   50:   

Through line 35, this is pretty much the same as what we had. 

Then in line 38, we add a DataForm control that gives us some nice way to view and edit a particular entity. 

Hit F5

image

This looks like the traditional master-details scenario.

image

Notice as we change items they are marked as “Dirty” meaning they need to be submitted back to the server.  You can make edits to many items, unmake some edits and the dirty marker goes away. 

Now we need to wire up the submit button. 

         private void Button_Click(object sender, RoutedEventArgs e)
        {
            dataForm1.CommitItemEdit();
            dds.SubmitChanges();
        }

We first need to commit the item we are currently editing, then we just submit changes.  This batches up the diffs and sends them back the server.  and our Update method is called.  Notice the dirty bit goes away.

Now, that is cool, but what about Data Validation?  Well, “for free” you get type level validation (if the filed is typed as an int you and you enter a string you get an error). 

image

Now let’s see if we can add a bit more.  We do that by editing the NorthwindDomainService.metadata.cs on the server.  It is important to do it on the server so that the system does all the validations are done once for a great UX experience and then again on the server for data integrity.  By the time your Update method is called on your DomainService you can be sure that all the validations have been done. 

Here are some of the validations we can apply.. 

             [Bindable(true, BindingDirection.OneWay)]
            public int EmployeeID;

            [RegularExpression("^(?:m|M|male|Male|f|F|female|Female)$", ErrorMessage = "Gender must be 'Male' or 'Female'")]
            public string Gender;


            [Range(0, 10000, 
                ErrorMessage = "Issues must be between 0 and 1000")]
            public Nullable<int> Issues;

            [Required]
            [StringLength(100)]
            public string Name;

Just rebuilding and running the app gives us great validation in the UI and in the middle tier. 

image

Notice how I can navigate between the errors and input focus moves to the next one. 

That was updating data, what about adding new data?

To do that, let’s explorer the new Silverlight 3 ChildWindow.

Right click on Views and add new Item Child Window

Notice it already has a OK and Cancel buttons.  We just need to add a DataForm.  Because we are bound to the same model, the DataForm will pick up all the same features as the update one we just looked at.

         <dataControls:DataForm x:Name="newEmployeeForm"
                               Width="500" Height="280" 
                               AutoEdit="True" 
                               AutoCommit="True"
                               VerticalAlignment="Top"       
                               CommandButtonsVisibility="None"
                               Header="New Employee">
        </dataControls:DataForm>

Now, in code behind we need to write this up by adding a class level field..

 public SuperEmployee NewEmployee {get; set;}

..initializing the instance in the constructor

             NewEmployee = new SuperEmployee();
            NewEmployee.LastEdit = DateTime.Now.Date;
            this.newEmployeeForm.CurrentItem = NewEmployee;

..handling the OK button

         private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            newEmployeeForm.CommitItemEdit();
            this.DialogResult = true;
        }

Great… now to see this thing we need to write up a button to show it.

 <Button Content="Add New" Width="100" Height="30"
        Margin="0,20,0,0" HorizontalAlignment="Left"
        Click="AddNew_Click" ></Button>
         private void AddNew_Click(object sender, RoutedEventArgs e)
        {
            var addNewWindow = new AddNewWindow();
            addNewWindow.Closed += addNewWindow_Closed;
            addNewWindow.Show();

        }
        void addNewWindow_Closed(object sender, EventArgs e)
        {
        }

image

Now let’s commit this change locally.

             var win = sender as AddNewWindow;
            var context = dds.DomainContext as NorthwindDomainContext;
            if (win.DialogResult == true)
            {
                context.SuperEmployees.Add(win.NewEmployee);
            }

From this, the Submit button will send this change to the server.

Authentication

Business applications often access very important data.  It is important that you can audit, restrict and control access to your data.  Let’s look at how to use .NET RIA Services and SL3 to do that. 

To enable authentication, let’s add a DomainService to the server project.  File New Domain Service and this time select an “empty domain service class”.

image

 using System.Web.Ria;
using System.Web.Ria.ApplicationServices;


namespace MyApp.Web
{
    [EnableClientAccess]
    public class AuthenticationService : AuthenticationBase<User>
    {
    }

    public class User : UserBase
    {

    }
}
.csharpcode {
 BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
   BACKGROUND-COLOR: #ffffff; FONT-FAMILY: consolas, "Courier New", courier, monospace; COLOR: black; FONT-SIZE: small
}
.csharpcode PRE {
   MARGIN: 0em
}
.csharpcode .rem {
  COLOR: #008000
}
.csharpcode .kwrd {
  COLOR: #0000ff
}
.csharpcode .str {
   COLOR: #006080
}
.csharpcode .op {
    COLOR: #0000c0
}
.csharpcode .preproc {
   COLOR: #cc6633
}
.csharpcode .asp {
   BACKGROUND-COLOR: #ffff00
}
.csharpcode .html {
   COLOR: #800000
}
.csharpcode .attr {
  COLOR: #ff0000
}
.csharpcode .alt {
   BACKGROUND-COLOR: #f4f4f4; MARGIN: 0em; WIDTH: 100%
}
.csharpcode .lnum {
 COLOR: #606060
}

Now, in the client project in App.xaml add a reference to this service

 <Application.Services>
    <appsvc:WebUserService x:Name="UserService" />
</Application.Services>

Now, that this is a service in the application, any page can access it.  Let’s access it from the MainPage.xml by adding a Welcome message.

  <TextBlock Text="welcome " Style="{StaticResource WelcomeTextStyle}"/>
 <TextBlock Text="{Binding Path=User.Name}" Style="{StaticResource WelcomeTextStyle}"/>

and one line of codebehind to databind to this AuthService. 

 LayoutRoot.DataContext = new UserContext();

image

Notice we are using NTLM (windows Auth) so my credentials are automatically hooked up.  We of course support basic auth out of the box and we have a fully pluggable system based on asp.net profile system. 

Now if I wanted to make sure that only authenticated users could access the data all I need to do is add an attribute to the domain service class. 

         [RequiresAuthentication]
        public IQueryable<SuperEmployee> GetSuperEmployees()

We could have equally easily restrict this to only users in a role. 

Update 4-2009: if you are having troubles with this be sure to verify that you either::

1.    Call UserService.Current.LoadUser();
      This will load the User at LoadUserCompleted.

2.    Modify Default.aspx.  This will load the User state automatically at startup.

  • Add <%@ Register Assembly="System.Web.Ria" Namespace="System.Web.Ria" TagPrefix="ria" %>
  • Rename <asp:Silverlight “ to <ria:SilverlightApplication “

Different Views

Now, let’s take take a look at putting different views on this same application.  One of the key elements to supporting different views is deeplinking.   Let’s look at adding that to this application.  

image

First, we need to add a textbox to display the deeplink for each of our super employees.

 <TextBlock Text="PermaLink:"></TextBlock>
<TextBox x:Name="PermalinkTextBox" Width="400" Height="25" TextWrapping="NoWrap" Foreground="#FFB4B4B4" />

And just wire this up in code behind..

         private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var emp = dataGrid1.SelectedItem as SuperEmployee;
            if (emp != null)
            {
                PermalinkTextBox.Text = Application.Current.Host.Source.ToString().Replace("ClientBin/MyApp.xap", "") +
                    "#/Views/HomePage.xaml;EmpId/" + emp.EmployeeID;
            }    
        }

image

Now we need to make the page smart enough to know about this deep link..

         protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            var qs = NavigationContext.QueryString;

            if (qs.ContainsKey("EmpId"))
            {
                dds.FilterDescriptors.Add(
                    new FilterDescriptor("EmployeeID",
                        FilterOperator.IsEqualTo, qs["EmpId"]));
            } 
        }

Now, run it…  cut and paste this url into a browser brings up exactly this record. 

image

OK, now that we have a deeplink, what can we do with it?  Well, you can send the link around in email, IM and in blogs. But what if you wanted the search engines to be able to find all the items in your site?  It turns out there is a standard format for this called a sitemap (https://sitemap.org).  Check out https://amazon.com/robots.txt for an example in the real world.

To add this to our application, say File\New Item in the Server Project and select the “Search Sitemap”.

image

This added a robots.txt and a sitemap.aspx.   Let’s look at customizing the sitemap.aspx file.  Notice we are using a new ASP.NET datasource control… that is designed to work with our DomainService.  This gives us server access to the same application logic. 

 <asp:DomainDataSource runat="server" ID="SitemapDataSource" 
    DomainServiceTypeName="MyApp.Web.NorthwindDomainService" 
    SelectMethod="GetSuperEmployees" />

---

 <url>
   <loc><%= this.Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped) %>/?EmpId=<%# Eval("EmployeeID") %></loc>
    <lastmod><%# Eval("LastEdit")%></lastmod>
</url>

Now run it… 

image

and the sitemap…

image

Cut and pasting the URL brings open that item.

image

OK… that works great.  Now the search engine can find every entity in our application.  But what will the search engine find when it gets there?  Search engines are great at parsing html, but dynamic content like we are seeing in this site or in rich ajax sites are a lot harder for search engines to deal with. 

image

The art of addressing this is called SEO (Search Engine Optimization).    Let’s go and apply SEO principles to our application, open up default.aspx and notice we are using a asp:SeoSilverlightApplication which has nice support for rendering alternate content.  Because we have modeled our application logic in a domain service class it is very easy to add a standards based HTML view. 

           <PluginNotInstalledTemplate>
              <asp:DomainDataSource runat="server" ID="DetailsDataSource" 
                           DomainServiceTypeName="MyApp.Web.NorthwindDomainService" 
                           SelectMethod="GetSuperEmployee">
                  <SelectParameters>
                      <asp:QueryStringParameter  Name="employeeID" Type="Int32" Querystringfield="EmpId" />
                  </SelectParameters>       
              </asp:DomainDataSource>
             <asp:ListView ID="EmployeeDetails" runat="server"  DataSourceID="DetailsDataSource" >
                <LayoutTemplate>                  
                  <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>    
                </LayoutTemplate>
                <ItemTemplate>
                   <h1><a href="<%# Eval("EmployeeID")%>" > <%# Eval("Name")%> </a>  </h1>
                     <table>
                     <tr> <td><b>Name:</b> <%# Eval("Name")%> </td> </tr>
                     <tr> <td><b>Publisher:</b> <%# Eval("Publishers")%> </td></tr>
                     <tr> <td><b>Gender:</b> <%# Eval("Gender")%></td> </tr>
                     <tr> <td><b>Origin:</b> <%# Eval("Origin")%></td> </tr>
                     <tr> <td><b>Issues:</b> <%# Eval("Issues")%></td> </tr>
                     <tr> <td><b>Sites: </b><%# Eval("Sites")%></td> </tr>
                  </table>
                </ItemTemplate>
             </asp:ListView>
           </PluginNotInstalledTemplate>

We need add a GetSuperEmployee domain method to support this code..  so back to our NorthwindDomainService and add:

         public IQueryable<SuperEmployee> GetSuperEmployee(int employeeID)
        {
            return this.Context.SuperEmployeeSet
                .Where(emp => emp.EmployeeID == employeeID);
        }

Now run it… Disable Silverlight in the browser because hey, search engines don’t have silverlight installed. 

image

Now hit refresh on a deep link…

image

Just for kicks… let’s run this application in lynx

image

image

Pretty cool, huh?  Now the search engine (or anyone without Silverlight) can access the data. 

But does it work?  well, try these searches:

Super employee placement Alfred (Yahoo)

Super Employee Placement Alfred (some other guy)

OK, that was a server view, let’s try a different client view..  Let’s export this data to the worlds best data manipulation tool – excel! 

Add the excel icon to view and template file to the project to the client root. 

Then add an “export to excel” button:

 <Button Width="170" Height="40"
    Margin="10,20,0,0" HorizontalAlignment="Left"
    Click="ExportToExcel_Click" >
       <StackPanel Orientation="Horizontal" >
         <Image Source="excelIcon.png" Height="35" Width="50"></Image>
           <TextBlock Text="Export to Excel" Padding="0,10,0,0"></TextBlock>
       </StackPanel>
 </Button>

And write out the excel file…  Notice this is all safe access as the Silverlight runtime proxies the user selection of the location to write the file.  The developer can not get direct access to the file location. 

 var context = dds.DomainContext as NorthwindDomainContext;
StreamResourceInfo s = Application.GetResourceStream(new Uri("excelTemplate.txt", UriKind.Relative));

var dialog = new SaveFileDialog();
dialog.DefaultExt = "*.xml";
dialog.Filter = "Excel Xml (*.xml)|*.xml|All files (*.*)|*.*";

if (dialog.ShowDialog() == false) return;

using (var sw = new StreamWriter(dialog.OpenFile()))
{

var sr = new StreamReader(s.Stream);
while (!sr.EndOfStream)
{
   var line = sr.ReadLine();
   if (line == "***") break;
   sw.WriteLine(line);
}

foreach (var emp in context.SuperEmployees)
{
   sw.WriteLine("<Row>");
   sw.WriteLine("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", emp.Name);
   sw.WriteLine("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", emp.Origin);
   sw.WriteLine("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", emp.Publishers);
   sw.WriteLine("<Cell><Data ss:Type=\"Number\">{0}</Data></Cell>", emp.Issues);
   sw.WriteLine("</Row>");
}
while (!sr.EndOfStream)
{
   sw.WriteLine(sr.ReadLine());
}

Run it…

image

image

This creates an excel file on the user selected location.

image

Opening it and doing a bit of formatting…

image

And the final view… out of browser. 

Open ApplicationManifest.xaml in the client project.  Add this xaml to out of browser enable it.

 <Deployment.ApplicationIdentity>
   <ApplicationIdentity
          ShortName="Super.Employee.Service"
          Title="Super Employee Placement Servcice">
        <ApplicationIdentity.Blurb>Description  blahh blahh blahh... </ApplicationIdentity.Blurb>
   </ApplicationIdentity>
</Deployment.ApplicationIdentity>

Run the application, right click and select “Install….”

image

image

Put a link on the desktop.. 

image

and run it!

image

Wow – that was a ton of stuff..   We have walked thought a few must-haves for a business applications..  this demo walked through each of these..  

Business Application Must-Haves

    1. Professional Feel
    2. Rich Data Query
    3. Validating Data Update 
    4. Authentication  
    5. Support Different Views

You can download the full demo files and check out the running application.

I’d love to have your feedback and thoughts!

Comments

  • Anonymous
    March 18, 2009
    Hi Brad, Very nice session as always! I think I will replay this for my next presentations in Montreal. Note: instead of Lynx your can use http://seo-browser.com Laurent.

  • Anonymous
    March 18, 2009
    Today at Mix09, I finished my talk on building business applications with Silverlight 3. The full video

  • Anonymous
    March 18, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Anonymous
    March 18, 2009
    Thoroughly Comprehensive! Great work! Lack of Printing was a downer, can you guys work on a workaround for that?

  • Anonymous
    March 19, 2009
    The comment has been removed

  • Anonymous
    March 19, 2009
    Brad, This is excellent, well-thought-out stuff that will truly help us developers (much moreso than a caret that can take a video brush). Is there any chance that RIA Services can be released separately (and earlier) than SL 3 itself? In general, automating client and server access to data in this fashion will save us all a ton of duplication and headaches. Obviously you know this, but, those of out here who have been working with SL through the early betas and up until now are eager so please release a go-live license as soon as you can!  I would take it with a list of known issues as caveats... --Pete

  • Anonymous
    March 19, 2009
    Over the last year I have been working on .NET RIA Services , a preview of which just went public at

  • Anonymous
    March 19, 2009
    The comment has been removed

  • Anonymous
    March 19, 2009
    SlowBrain - Yes, beleive me, I understand.. things are moving very fast.  The good news is that the SL2 way of doing this is still very viable... you can keep doing them even after SL3RIA Services ships.   There are no "breaking" changes here.  Just some new options.  

  • Anonymous
    March 19, 2009
    The comment has been removed

  • Anonymous
    March 19, 2009
    Kade - yes, good point, this is a simple example just to explain the concepts.  we have thought about issues of doing this pattern at-scale.  We'll get some more examples out soon.. but I'd love to hear any feedback on where the issues might be.  

  • Anonymous
    March 19, 2009
    The comment has been removed

  • Anonymous
    March 19, 2009
    Just watched the session on demand and it was very cool - I would have applauded even more often that the folks in the audience did :) .NET RIA Services is a great thing and in fact, only this bit got me excited about Silverlight again (after the keynote, I was kind of disappointed because feature-wise, SL3 will be similar to Flex and in my opinion, Flex has a much simpler development model). One question though: is .NET RIA services tightly bound to Silverlight or would it be possible to plug in some different client-side technology? (Thinking Flex here; we love .NET on the server side but our company is set on Flex for client-side development.)

  • Anonymous
    March 20, 2009
    What was the news about the Activity control?  You had mentioned blogging about it in your sesion.  Are there others planned similar to Activity?

  • Anonymous
    March 20, 2009
    In my Mix09 talk I used a brand new Activity control that we are working on.&#160;&#160;&#160; I had

  • Anonymous
    March 21, 2009
    Yesterday at Mix we announced .NET RIA Services… We are very early in the development cycle for .NET

  • Anonymous
    March 21, 2009
    Brad, is there any indication of the release of a SL beta 3 client install so I can let my client try out our SL3 prototypes ?

  • Anonymous
    March 21, 2009
    The comment has been removed

  • Anonymous
    March 21, 2009
    Web Writing IE8 accelerator in 15 minutes (bookmark on Delicious) Constructing Web Interfaces on the

  • Anonymous
    March 21, 2009
    Brad, Great talk and article. I've been playing around with the UserService stuff and can't quite work it out with forms authentication. In the following code, isauthenticated is always false, though my asp.net page reckons the user is authenticated. userService = Application.Current.Services["userService"] as UserService;            userService.LoadUser(); bool isauthenticated =             userService.User.IsAuthenticated; I can submit login details in the silverlight page through the UserContext, and then all works fine. How does the separation between Client-authentication and server-side authentication work?

  • Anonymous
    March 22, 2009
    Stephen, It's tough to tell exactly what you've got going with the UserService, but here's the basic design. The Authentication DomainContext that gets generated from the Authentication DomainService you created has some additional state to identify it. The WebUserService you create (in Appl.xaml) looks for that state and creates a context under the covers to use for communicating with the server. Therefore, every call you make via UserService.Current gets routed through a DomainContext to your custom AuthenticationService. The default implementation of the AuthenticationService (in AuthenticationBase) reads the IIdentity values out of HttpContext.Current.User.Identity and serializes them back to the client. The likely cause of discrepancies between the client and server is that you haven't (re)loaded since the state changed. If you're curious about the user data you're passing back to the client, you can examine the state by overriding AuthenticationBase.GetAuthenticatedUser or AuthenticationBase.GetAnonymousUser in your Authentication Service. Hope that helps. Kyle

  • Anonymous
    March 22, 2009
    The comment has been removed

  • Anonymous
    March 22, 2009
    Dévoilé au grand jour la semaine dernière, Silverlight 3 apporte son lot de nouveautés (support de l’accélération

  • Anonymous
    March 22, 2009
    The comment has been removed

  • Anonymous
    March 22, 2009
    Steve, thanks for this very useful article.  This is just a quick comment to say some of the typos in the article might challenge non English speakers.  For instance this block of text: These .NET RIA Services model your UI-tier application logic and encapsulate your access to varies data sources from traditional relational data to POCO (plain old CLR objects) to could services such as Azure, S3, etc via REST, etc.  One of great thing about this is that you can move from an on premises Sql Server to a Azure hosted data services  without having to change any of your UI logic. Let’s look at how easy it is to great these .NET RIA Services.  

  • Anonymous
    March 22, 2009
    ...meant to say English as a second language.  As non English speakers it would all be a challenge!

  • Anonymous
    March 23, 2009
    Hi Stephen, Your code looks fine and far as I can tell it should work. Here is what might be happening ... Are you looking for IsAuthetnicated to turn to true as soon as you call LoadUser? LoadUser being an async call you will need to wait for the Completed event before IsAuthenticated will actually return true. Let me know if that was the issue. Also we now have a 'Silverlight Business Application Template' that sets up the form authentication pieces for you in a VS project template. You might want to try out the template and the walkthrough that goes with it. It all available here - http://code.msdn.microsoft.com/RiaServices Saurabh

  • Anonymous
    March 23, 2009
    Leo, the error message you're seeing comes from an IncludeAttribute that you must have in your model? If you have an [Include("IDBookCategory", "ID")] applied to your BookCategory.Book association, you need to fix the path property after your rename.

  • Anonymous
    March 23, 2009
    You asked for feedback on where the pain points/issues for LOB apps are. Please see my latest blog on 'solving' the combobox with lookup tables pattern that I did with the help of Luke Longley from the (now defunct) Alexandria forum: http://tinyurl.com/dm98ms (elastic layout looks better). It's dog ugly code <g> and exhibits code smell, but it works. Remember how easy it was to hook up a lookup table to a parent relationship in Access 97? I think that's what most LOB app builders would like. They're handed a normalized database and told to knock something out. SL3 in Intranet scenarios like this could really relieve some app backlog.

  • Anonymous
    March 23, 2009
    The comment has been removed

  • Anonymous
    March 23, 2009
    Jeff, Can you provide more details on what project you are building? Also if you would be willing to share your project with us to debug can you mail me directly through my blog.

  • Anonymous
    March 23, 2009
    #.think.in infoDose #22 (16th Mar - 20th Mar)

  • Anonymous
    March 24, 2009
    The comment has been removed

  • Anonymous
    March 24, 2009
    Steve -- I am afriad this is a "bugfeature" in the Silverlight runtime.  All custom validations through an exception which break a runtime if run under the debugger.  if you just play-through it, it should work or run it without the debugger  (ctrl-F5) you should be fine.  

  • Anonymous
    March 24, 2009
    Thanks Brad. I do think there is a problem here - using your above example on close with the ChildWindow - even though I have a validation exception fire, it still attempts to save the new record. It's still getting down to the context.SubmitChanges(); call on the form close.

  • Anonymous
    March 24, 2009
    NM Brad, I did resolve that problem by inserting the following code: if (!win.newLastNameForm.ErrorSummary.HasErrors)                {                    context.LastNames.Add(win.LastName);                    context.SubmitChanges();                } I make sure there are no errors first by checking the ErrorSummary.HasErrors property

  • Anonymous
    March 25, 2009
    Are there walkthroughs on how to customize the template?  You had a nice looking template you mentioned Corrina Black sent you.  Thanks.

  • Anonymous
    March 25, 2009
    Brad, Thanks for the post as well as the presentation.  I am curious to use this with Prism V2.  Can you still get deep link if you are not using the SL3 navigation piece?  I would also like to know if RIA Services can be accessed from other projects so that I could have all of my data access and authentication/authorization available in all of my modules/projects, do you know if this is possible?  It seems like the model you are showing requires everything to be in a single project?

  • Anonymous
    March 25, 2009
    Is there any way to prevent navigation using the RIA Services?  For example, you have public pages and private pages based on authorization.  I would like to not even let a person navigate to the screen. Thx.

  • Anonymous
    March 25, 2009
    Thanks for great posting! When I'm using a 'bad' URL and without debugger (Ctrl+F5) it works as in the example. When I run it with debugger (F5) I get a blank (white) screen (IE 7).  I would expect some kind of exception caught by the debugger (I have set to Break on all exceptions..). Any ideas are welcome!

  • Anonymous
    March 25, 2009
    Kevin - Yes, you can do a little bit of code behind to prevent navigating to pages unless the user is logged inin a particular role.  We will post a sample on that soon.

  • Anonymous
    March 25, 2009
    Matt – Yes, we worked with the Prism team to make sure what we were building worked well with their model.  I’ll look into get an example posted..   Right now you’d have to do some build work to make the separate project’s work.. that is something we are going to streamline in a future CTP…

  • Anonymous
    March 25, 2009
    The comment has been removed

  • Anonymous
    April 02, 2009
    Hi Brad, Does ErrorSummary controls works only with Dataform/RIA? I have a simple username textbox and I want to validate that user can't click "Submit" button without entering username. How can I use ErrorSummary control in this case? Most of the examples available on the web always use RIA/dataform combination and not the simple scenario like the one I am looking for. Any suggestions? Thanks, Mahesh.

  • Anonymous
    April 03, 2009
    >> Does ErrorSummary controls works only with Dataform/RIA? Mahesh,  No, They work standalone..  You can find some examples of that here: http://silverlight.net/samples/sl2/toolkitcontrolsamples/run/default.html

  • Anonymous
    April 05, 2009
    A chegada do padrão MVC para o ASP.Net tem sido bastante comentada entre arquitetos. Temos recebido questões

  • Anonymous
    April 10, 2009
    The comment has been removed

  • Anonymous
    April 12, 2009
    I must say I'm really impressed where Silverlight is going and I couldn't stop smiling while watching your session on demand. Especially the RIA services really make me happy. I'm currently working on a LOB using SL2 and a WCF backend, which is really working well for me. I thought trying to upgrade to SL3 and implement a view in the app using the RIA services and the grouping / filtering features. I about an hour I had it up and working and am amazed with the little amount of code I had to write and how flexible it is. I've one question though. Is it possible to specify a different display name for a GroupDescriptor? Right now I group on a property named 'Period', but I want it displayed as 'Week' in the grid.

  • Anonymous
    April 14, 2009
    Sanders - thanks!   I am gald this stuff helps. >>I've one question though. Is it possible to specify a different display name for a GroupDescriptor? Right now I group on a property named 'Period', but I want it displayed as 'Week' in the grid. At SL3 RTM we will be respecting the Display attribute for this.  in the mean time, I think you can check out the GroupName property... Hope that helps.

  • Anonymous
    April 14, 2009
    Sanders - thanks!   I am gald this stuff helps. >>I've one question though. Is it possible to specify a different display name for a GroupDescriptor? Right now I group on a property named 'Period', but I want it displayed as 'Week' in the grid. At SL3 RTM we will be respecting the Display attribute for this.  in the mean time, I think you can check out the GroupName property... Hope that helps.

  • Anonymous
    April 17, 2009
    Great sample app that works really well with only one problem - I can't get the AddToSuperEmployeeSet to work - no errors but just does not write to db Has anyone else had this problem or is it just 'muggins'?

  • Anonymous
    April 18, 2009
    Brad, thanks for you reply! >> At SL3 RTM we will be respecting the Display attribute for this.  in the mean time, I think you can check out the GroupName property... I think I'm missing something here. Should GroupName be a property on a GroupDescriptor? I can only see GroupDescriptor have a single property: PropertyPath. I hope you can point me in the right direction.

  • Anonymous
    April 28, 2009
    Hi there all, great to see you are the inaugural SDDN User Group in Perth and thanks Stephen for the

  • Anonymous
    April 29, 2009
    Brad, concerning your reply to the GroupName issue: >> At SL3 RTM we will be respecting the Display attribute for this.  in the mean time, I think you can check out the GroupName property... I can't figure out to what GroupName propety you are refeering to, and how to link it to the GroupDescriptor to replace the displaying Name. I am evaluating Silverlight 3 for my team, we are starting a new project right now, i would like to provide a complete example for them, and this point is as simple as crucial. I hope you can answer as soon as posible. Thank you very much!

  • Anonymous
    May 01, 2009
    I would like to use DomainServices not only in the PresentationLogic-Tier (from the web project towards the RIA client), but also between a BusinessLogic-Tier (from that web towards the presentation logic web project) and the PresentationLogic tier. In other words, how can I use PeerToPeer Communication between two DomainService Projects? BTW, I would like to be able to have two Web(Role) projects and being able to communicate between both bidirectional only via their included DomainServices. Reaching this I would be able to let it run either in Azure or OnPremise without changing code. Does that make sense? Any support today? Thanks

  • Anonymous
    May 03, 2009
    The comment has been removed

  • Anonymous
    May 03, 2009
    When you look at it more closely in Blend, there is another exception hiding inside and it's 'Cannot create an instance of "MyDomainContext", so the designer tries to instantiate it, hmm.

  • Anonymous
    May 04, 2009
    Hi Pablo, To change the string's value, set the PropertyName property on the DataGridRowGroupHeader.  To get access to it, subscribe to the LoadingRowGroup event on DataGrid. Hope this helps.

  • Anonymous
    May 08, 2009
    This is an excellent article introducing RIA Brad! I really enjoyed the article. I'm a little bit dissapointed that the entry point for the client is through a control. I hope the connection point also available through an API. Therefore I can 'map' my View Model to RIA Data Source instead.

  • Anonymous
    May 14, 2009
    I am porting an ASPX.NET app to Silverlight 2.  It uses Linq2Sql, so I created a web service to make the data available to SilverLight 2.  On the Linq2Sql side, each class has members that directly get records from other tables.  These are not keys, but the name of the other table serves as the name of a member that is a direct reference.  These members are missing from the auto-generated web service in the .Web project, so all fields referring to these properties are blank in the SilverLight DataGrid. Would SilverLight 3/RIA fix this problem?  (I.e. would those properties   Is there another workaround, other than re-writing all the queries?

  • Anonymous
    May 18, 2009
    Scott Morrison just published his demo files for his talks at Teched..&#160; He did some great work on

  • Anonymous
    May 20, 2009
    Thanks to everyone who came out to hear my presentation at the South Florida Architects meeting last

  • Anonymous
    May 26, 2009
    While the feedback on .NET RIA Services has been great, many people have commented on the way we store

  • Anonymous
    May 28, 2009
    @dzambi, I am getting the same error message as you.  It seems to be a problem with all projects that I either create or download.  Not sure what is up.  Could it be a Vista thing?  

  • Anonymous
    May 31, 2009
    NET RIA Service, #Part1 install.

  • Anonymous
    May 31, 2009
    You can download the Silverlight 3 and RIA Services tools here but be aware that they cannot be installed

  • Anonymous
    May 31, 2009
    Brad, in your post you mentioned a "library of app.xaml" files. Is this library included in the current RIA Services preview? If not, where can I find it? Thanks!

  • Anonymous
    June 01, 2009
    呵呵,终于找到你了,这篇文章讲解的很棒,我一定得研究它!!!

  • Anonymous
    June 02, 2009
    Hi Brad, How to create add new row functionality when using DomainDatasource, when to add new empty object to collection. This is what  i need to achive. The Last Row in datagrid should be empty new row. When user fills that row, i need to create a new blank row. I need to do this with DomainDataSource Control. Thanks

  • Anonymous
    June 02, 2009
    Hi Brad, There is a problem with the autocomplete control in my firefox, the application simple has gone when I started to fill the textbox. I've tryied with IE and this works. I've even reproduced it here in a VS project and i've got the same error. My firefox config is: Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10 (.NET CLR 3.5.30729) BTW, nice presentation, congratulations. =) Thanks!

  • Anonymous
    June 04, 2009
    This is all very nice indeed. However if I wanted to add some additional very useful (and business required) features

  1. An undo button to set the property values of the currently selected item back to original (unmodified) values and
  2. An undo key (eg esc key) to reset the current field (control text) back to its original (unmodified) value state. In tradition ASP.NET / WinForms etc Biz Apps a DataTable class containing a DataRow set would typically be used to contain the data and it is a trivial excercise to pass the DataRow set an check the DataRowState (Added, Modified, Deleted etc) and undo any actions (dataRow[current].RejectChanges(). One can also easily examine both the original and current values of each column value property (specifically for the currently selected control) and again simply set the current value to the original value when a predefined undo key is pressed (probably typically the esc key). These states are obviously being maintained somewhere in the plumbing but how in RIA/SL3 can we get under the bonnet and access these states to achieve this same typical essential business functionality?
  • Anonymous
    June 05, 2009
    There seems to be a problem with ChildWindow.  When I try to create one and say Show() all the controls on the screen just disappear.  I thought maybe I was coding it wrong, but your demo app shows me the exact same thing.   http://www.hanselman.com/abrams/#/Views/HomePage.xaml When you click on "Add New" all the controls disappear and there is nothing but a white screen.  Any ideas?

  • Anonymous
    June 05, 2009
    More info:  It seems to just affect my firefox but IE 8 seems to be working.  I guess that is why they call it beta?  Anyone experienced this?

  • Anonymous
    June 05, 2009
    More info:  It seems to just affect my firefox but IE 8 seems to be working.  I guess that is why they call it beta?  Anyone experienced this?

  • Anonymous
    June 08, 2009
    I just finished up the keynote at VSLive in Vegas.&#160;&#160;&#160;&#160; I did an update of my Building

  • Anonymous
    June 12, 2009
    ヒマだょ…誰かかまってぉ…会って遊んだりできる人募集!とりあえずメール下さい☆ uau-love@docomo.ne.jp

  • Anonymous
    June 12, 2009
    ヒマだょ…誰かかまってぉ…会って遊んだりできる人募集!とりあえずメール下さい☆ uau-love@docomo.ne.jp

  • Anonymous
    June 14, 2009
    カワイイ子ほど家出してみたくなるようです。家出掲示板でそのような子と出会ってみませんか?彼女たちは夕食をおごってあげるだけでお礼にHなご奉仕をしてくれちゃったりします

  • Anonymous
    June 14, 2009
    カワイイ子ほど家出してみたくなるようです。家出掲示板でそのような子と出会ってみませんか?彼女たちは夕食をおごってあげるだけでお礼にHなご奉仕をしてくれちゃったりします

  • Anonymous
    June 15, 2009
    あなたは右脳派?もしくは左脳派?隠されたあなたの性格分析が3分で出来ちゃう診断サイトの決定版!合コンや話のネタにも使える右脳左脳チェッカーを試してみよう

  • Anonymous
    June 15, 2009
    あなたは右脳派?もしくは左脳派?隠されたあなたの性格分析が3分で出来ちゃう診断サイトの決定版!合コンや話のネタにも使える右脳左脳チェッカーを試してみよう