If you haven’t already, read the article this example is based on or start at the beginning of this example.
NOTE: I’m using WCF RIA Services for Silverlight 4 (not the SP1 beta) available at http://www.silverlight.net/getstarted/riaservices/ for this example.
Add a Silverlight Business Application project called CSSCustomerEdit to your solution:
Copy the connectionStrings element from CSSModel’s App.Config to CSSCustomerEdit.Web’s Web.config (make sure to put it after configSections, which needs to be the first element in configuration):
Add references to CSSModel and System.Data.Entity to CSSCustomerEdit.Web’s references:
Set Copy Local to true for the assemblies under System.ServiceModel.DomainServices:
In Solution Explorer, right-click on the Services folder and Add | New Item … choose Domain Service Class and call it CSSDomainService (important to end the name in “Service”):
Select all the entities and enable editing for each:
Now it’s time to implement the actual behavior of this use case. Here’s a nice big old chunk of code for handy copy-and-pasting … read Step 5 in the article to understand it:
[EnableClientAccess()]
public class CSSDomainService : LinqToEntitiesDomainService<CSSModelContainer>
{
private int myCompany = 1; // TODO: set myCompany during authentication
public Company GetMyCompany()
{
return this.ObjectContext.Companies.Single(c => c.Id.Equals(myCompany));
}
public void UpdateCompany(Company currentCompany)
{
// TODO: ensure user only edits authorized companies
this.ObjectContext.Companies.AttachAsModified(currentCompany, this.ChangeSet.GetOriginal(currentCompany));
}
public IQueryable<Story> GetMyStories()
{
return this.ObjectContext.Stories.Where(s => s.CompanyId.Equals(myCompany));
}
public void InsertStory(Story story)
{
story.CompanyId = myCompany;
story.Id = -1; // database will replace with next auto-increment value
if ((story.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(story, EntityState.Added);
}
else
{
this.ObjectContext.Stories.AddObject(story);
}
}
public void UpdateStory(Story currentStory)
{
// TODO: ensure user only edits authorized stories
this.ObjectContext.Stories.AttachAsModified(currentStory, this.ChangeSet.GetOriginal(currentStory));
}
public void DeleteStory(Story story)
{
// TODO: ensure user only deletes authorized stories
if ((story.EntityState == EntityState.Detached))
{
this.ObjectContext.Stories.Attach(story);
}
this.ObjectContext.Stories.DeleteObject(story);
}
}
Here’s an image of the class with syntax highlighting:
For the UI, I want to copy the Home and About pages (which would presumably have descriptions of the app at some point) and add functionality. The UI is implemented in CSSCustomerEdit. Start by adding references to System.Windows.Controls.Data and System.Windows.Controls.DomainServices. Then right-click on the Views folder, Add | New Item to create a new Silverlight Page named Company (singular – users can only edit their own company). Do the same for Stories (plural):
In ApplicationStrings.resx (under the Assets | Resources folder), add strings for CompanyPageTitle and StoriesPageTitle, then build the solution:
Copy the XAML within the Grid element from Home.xaml to Company.xaml and Stories.xaml. Change “Home” appropriately:
In MainPage.xaml, copy the Rectangle and the HyperLinkButton that follows and paste it twice after. Change the x:Names of each to be unique and change “About” appropriately:
Run it at this point to make sure your copies worked … especially for xaml apps, I like to add functionality bit-by-bit and run it a lot to make sure I didn’t break something.
Editing XAML directly is a bit cumbersome, but the results are very powerful once you get used to it. It might be helpful to download the code sample from the article and look at complete .xaml files.
Open Company.xaml, and add the following three namespaces amongst the other xmlns entries:
xmlns:domainservices="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"
xmlns:css="clr-namespace:CSSCustomerEdit.Web.Services"
xmlns:dataform="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
Add the following as the first element within the Grid element:
<domainservices:DomainDataSource x:Name="MyData" AutoLoad="True" QueryName="GetMyCompany">
<domainservices:DomainDataSource.DomainContext>
<css:CSSDomainContext />
</domainservices:DomainDataSource.DomainContext>
</domainservices:DomainDataSource>
This sort of thing always looks odd to me in the declarative syntax of XAML, but ultimately it creates a bindable data set called MyData that calls the GetMyCompany query I defined earlier in CSSDomainService (magically “linked” to CSSDomainContext by WCF RIA Services).
Replace the second TextBlock with a DataForm. You can get something interesting with as little as this:
<dataform:DataForm x:Name="CompanyDetails" CurrentItem="{Binding ElementName=MyData, Path=Data.CurrentItem}"></dataform:DataForm>
which binds a DataForm to the MyData element we created above. However, after much tinkering with attribute values (IntelliSense is very helpful here), I settled on the following, which includes an EditTemplate and an EditEnded event:
<dataform:DataForm x:Name="CompanyDetails"
Header="Company Details"
AutoGenerateFields="False"
AutoEdit="True"
AutoCommit="False"
CommandButtonsVisibility="Commit,Cancel"
CommitButtonContent="Save"
IsEnabled="True"
IsReadOnly="False"
CurrentItem="{Binding ElementName=MyData, Path=Data.CurrentItem}"
EditEnded="CompanyDetails_EditEnded"
Margin="0,12,0,0">
<dataform:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataform:DataField Label="Name: ">
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</dataform:DataField>
<dataform:DataField Label="Country: ">
<TextBox Text="{Binding Country, Mode=TwoWay}" />
</dataform:DataField>
</StackPanel>
</DataTemplate>
</dataform:DataForm.EditTemplate>
</dataform:DataForm>
Here’s an image of the entire Company.xaml file with syntax highlighting (and key parts circled):
Before you can run this, you’ll need to right click on CompanyDetails_EditEnded and Navigate to Event Handler. In that method, add the following to make your edits persistent:
if (MyData.HasChanges)
{
MyData.SubmitChanges();
}
Stories is similar but adds a DataGrid above the DataForm. this requires an additional namespace entry:
xmlns:datagrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
The domain context is the same as above, but change GetMyCompany to GetMyStories.
I replaced the second TextBlock with a DataGrid followed by a DataForm:
<datagrid:DataGrid x:Name="StoryGrid" Height="120" ItemsSource="{Binding ElementName=MyData, Path=Data}" AutoGenerateColumns="False">
<datagrid:DataGrid.Columns>
<datagrid:DataGridTextColumn Header="Headline" Binding="{Binding Headline}" />
</datagrid:DataGrid.Columns>
</datagrid:DataGrid>
<dataform:DataForm x:Name="StoryDetails" Header="Story Details" AutoGenerateFields="False" AutoEdit="True"
AutoCommit="False" CommandButtonsVisibility="All" CommitButtonContent="Save" IsEnabled="True"
IsReadOnly="False" ItemsSource="{Binding ElementName=MyData, Path=Data}"
EditEnded="StoryDetails_EditEnded" Margin="0,12,0,0">
<dataform:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataform:DataField Label="Headline: ">
<TextBox Text="{Binding Headline, Mode=TwoWay}" />
</dataform:DataField>
<dataform:DataField Label="Detail: ">
<TextBox Text="{Binding Detail, Mode=TwoWay}" TextWrapping="Wrap" MinHeight="160" AcceptsReturn="True" />
</dataform:DataField>
</StackPanel>
</DataTemplate>
</dataform:DataForm.EditTemplate>
</dataform:DataForm>
Here is an image of the entire Stories.xaml with syntax highlighting:
Rename CSSCustomerEditTestPage.aspx to Default.aspx so you don’t have an ugly URL.
And the final result in action:
Publish to Azure
Again, I’m deviating from the article here and sharing a single Azure Services project across all examples. So in solution explorer, go to the CSSService project and delete CSSPublic as the associated web role, then add CSSCustomerEdit.Web:
Publish to Azure as in step 3; verify that your app works in the cloud, then stop AND DELETE the web role in the Windows Azure account portal so that you don’t incur charges.
Continue to next step – running all three use cases in a single web role