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!