Silverlight + RIA Service的SUID的實例
1、準備工作
<providers>
<clear />
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/Vega" />
</providers>
</membership>
<profile>
<providers>
<clear />
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
<properties>
<add name="FriendlyName" />
</properties>
</profile>
<roleManager enabled="true">
<providers>
<clear />
<add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/Vega" />
<add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
</providers>
</roleManager>
我用的連接字符串名字是ApplicationServices,如果沒安裝ASP.NET的SQL數據,可以用Visual Studio 命令提示(2010) (在開始菜單的Visual Studio Tools里)調出命令提示符輸入aspnet_regsql,調出“ASP.NET SQL SERVER安裝向導”,而在運行-cmd里因為沒有相關環境變量,是調不出安裝向導的。
新建一個ADO.NET實體數據模型。
編譯一下,然后新建Domain Service Class,勾上Course,如果不編譯,那么數據實體模型是找不到的。我們需要Microsoft Silverlight 4 Toolkit,http://silverlight.codeplex.com/,沒安裝的話要先安裝。
2、用戶登錄和角色
<!-- welcomeText.Text property's binding is setup in code-behind -->
<TextBlock x:Name="welcomeAdminText" Style="{StaticResource WelcomeTextStyle}" VerticalAlignment="Center"/>
<!-- welcomeText.Text property's binding is setup in code-behind -->
<TextBlock Text=" | " Style="{StaticResource SpacerStyle}"/>
<HyperlinkButton TargetName="ContentFrame" Content="課程" VerticalAlignment="Center" Foreground="White" NavigateUri="/Courses"/>
<TextBlock Text=" | " Style="{StaticResource SpacerStyle}"/>
<HyperlinkButton TargetName="ContentFrame" Content="學員" VerticalAlignment="Center" Foreground="White" NavigateUri="/Students"/>
<TextBlock Text=" | " Style="{StaticResource SpacerStyle}"/>
<HyperlinkButton TargetName="ContentFrame" Content="排課" VerticalAlignment="Center" Foreground="White" NavigateUri="/Schedules"/>
<TextBlock Text=" | " Style="{StaticResource SpacerStyle}"/>
<Button x:Name="adminLogoutButton" Content="{Binding ApplicationStrings.LogOffButton, Source={StaticResource ResourceWrapper}}"
Click="LogoutButton_Click"
Style="{StaticResource LoginRegisterLinkStyle}"
IsEnabled="{Binding Authentication.IsLoggingOut, Converter={StaticResource NotOperatorValueConverter}}" />
</StackPanel>
然后增加一個狀態,叫做adminLoggedIn,在<vsm:VisualStateGroup x:Name="loginStates">代碼短里加上adminLoggedIn的代碼:
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="logoutControls" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00.0000000">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="loginControls" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00.0000000">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
不要忘了在其他的狀態里加上把adminControls設置為不顯示,只有在adminLoggedIn才顯示管理員的功能導航。在構造器函數里加上welcomeAdminText的綁定:
{
this.InitializeComponent();
this.welcomeText.SetBinding(TextBlock.TextProperty, WebContext.Current.CreateOneWayBinding("User.DisplayName", new StringFormatValueConverter(ApplicationStrings.WelcomeMessage)));
this.welcomeAdminText.SetBinding(TextBlock.TextProperty, WebContext.Current.CreateOneWayBinding("User.DisplayName", new StringFormatValueConverter(ApplicationStrings.WelcomeMessage)));
this.authService.LoggedIn += this.Authentication_LoggedIn;
this.authService.LoggedOut += this.Authentication_LoggedOut;
this.UpdateLoginState();
}
修改UpdateLoginState函數,判斷如果當前用戶角色有admin,就顯示admin的功能導航按鈕:
private void UpdateLoginState()
{
if (WebContext.Current.User.IsAuthenticated)
{
VisualStateManager.GoToState(this, (WebContext.Current.Authentication is WindowsAuthentication) ?
"windowsAuth" :
this.authService.User.IsInRole("admin") ? "adminLoggedIn" : "loggedIn", true);
}
else
{
VisualStateManager.GoToState(this, "loggedOut", true);
}
}
通過ASP.NET 網站配置加上個admin角色和用戶。
好了,用戶程序登錄之后判斷有admin角色,登錄后就顯示管理員的功能導航了。
3、綁定數據源
![](https://imageproxy.pixnet.cc/imgproxy?url=https://pic002.cnblogs.com/img/subwayline13/201008/2010083118114574.png)
<TextBox Margin="0,15,0,0" x:Name="tbFilterCourseName" Height="23" VerticalAlignment="Top" HorizontalAlignment="Right" Width="120" />
好了,然后拖一個DataPaper上去實現分頁,也和DomainDataSource綁定上 Source="{Binding Data, ElementName=courseDomainDataSource}"。
我們還要加上一排CheckBox用于多選,跟ASP.NET的GridView差不多,有一種DataGridTemplateColumn。CheckBox直接綁定實體對象,加上Tag="{Binding}",然后給CheckBox加上事件,在選中的把綁定的對象加入到一個List里,取消選中的時候在取出來,維護一個要刪除對象的列表。
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
var checkBox = sender as CheckBox;
this.deletedCourses.Add(checkBox.Tag as Course);
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
var checkBox = sender as CheckBox;
this.deletedCourses.Remove(checkBox.Tag as Course);
}
另外加一個刪除全部的按鈕。下面是BusyIndicator的全部代碼:
<Grid>
<sdk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Data, ElementName=courseDomainDataSource}" x:Name="courseDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Margin="30,47,0,33" IsReadOnly="True">
<sdk:DataGrid.Resources>
<DataTemplate x:Name="CheckBoxCellTemplate">
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" Tag="{Binding}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
</DataTemplate>
</sdk:DataGrid.Resources>
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="30">
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<StaticResource ResourceKey="CheckBoxCellTemplate"/>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTextColumn x:Name="courseNameColumn" Binding="{Binding CourseName}" Header="課程名稱" Width="SizeToCells" MinWidth="70" />
<sdk:DataGridTextColumn x:Name="titleColumn" Binding="{Binding Title}" Header="標題" Width="SizeToCells" MinWidth="100" />
<sdk:DataGridTextColumn x:Name="providerColumn" Binding="{Binding Provider}" Header="內容提供商" Width="SizeToCells" MinWidth="70" />
<sdk:DataGridTextColumn x:Name="teachersColumn" Binding="{Binding Teachers}" Header="主講老師" Width="SizeToCells" MinWidth="70" />
<sdk:DataGridTextColumn x:Name="publishDateColumn" Binding="{Binding PublishDate}" Header="發布時間" Width="SizeToCells" MinWidth="70" />
<sdk:DataGridTextColumn x:Name="createTimeColumn" Binding="{Binding CreateTime}" Header="創建時間" Width="SizeToCells" MinWidth="70" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<sdk:DataPager Margin="30,444,0,10" x:Name="dataPager1" PageSize="10" Source="{Binding Data, ElementName=courseDomainDataSource}" />
<TextBlock Margin="0,18,137,0" Text="課程名稱" FontSize="12" HorizontalAlignment="Right" Width="48" Height="23" VerticalAlignment="Top" />
<TextBox Margin="0,15,0,0" x:Name="tbFilterCourseName" Height="23" VerticalAlignment="Top" HorizontalAlignment="Right" Width="120" />
<Button Content="刪除所選" Height="23" HorizontalAlignment="Left" Margin="30,15,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</toolkit:BusyIndicator>
然后,要把DomainDataSource.FilterDescriptors的篩選和我剛才加的TextBox綁定起來,這樣就自動搜索了。
<riaControls:DomainDataSource.SortDescriptors>
<riaControls:SortDescriptor Direction="Descending" PropertyPath="CourseID" />
</riaControls:DomainDataSource.SortDescriptors>
<riaControls:DomainDataSource.FilterDescriptors>
<riaControls:FilterDescriptor Operator="Contains" PropertyPath="CourseName" IgnoredValue="" Value="{Binding Text, ElementName=tbFilterCourseName}" />
</riaControls:DomainDataSource.FilterDescriptors>
<riaControls:DomainDataSource.DomainContext>
<my1:VegaContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
如果不加FilterDescriptors,那么分頁的時候報錯,因為分頁前必須Order,也可以在后臺加一個默認排序:
{
return this.ObjectContext.Courses.OrderByDescending<Course, int>(c => c.CourseID);
}
效果如下:
![](https://imageproxy.pixnet.cc/imgproxy?url=https://pic002.cnblogs.com/img/subwayline13/201008/2010083118315457.png)
4、刪除全部選擇的項目。
{
var result = MessageBox.Show("確定刪除該課程?刪除后不可恢復!", "提示", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
foreach (var course in this.deletedCourses)
{
this.courseDomainDataSource.DataView.Remove(course);
}
this.deletedCourses.Clear();
this.courseDomainDataSource.SubmitChanges();
}
}
5、上傳圖片
public class UploadService : DomainService
{
public string Upload(byte[] stream,string extension)
{
var rootPath = HttpContext.Current.Server.MapPath("/Uploads");
var datePath = DateTime.Now.ToString("yyyyMMdd");
var dirPath = Path.Combine(rootPath, datePath);
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
var fileName = Guid.NewGuid().ToString() + extension;
using (var fileStream = new FileStream(
Path.Combine(dirPath, fileName), FileMode.Create, FileAccess.Write))
{
fileStream.Write(stream, 0, stream.Length);
fileStream.Flush();
fileStream.Close();
}
return "/Uploads/" + datePath + "/" + fileName;
}
}
代碼很簡單,就一個方法,將傳過來的字節流保存到服務器上,然后返回為這個文件自動生成的路徑。
在服務器端建一個DomainService后,客戶端會自動生成調用的代碼,真的很方便。在客戶端直接實例化一個Context就能用了 private UploadContext uploadContext = new UploadContext();在Silverlight建一個用戶控件,專門負責上傳和現實圖片。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<toolkit:BusyIndicator Name="busyIndicator">
<StackPanel Margin="0" Orientation="Horizontal">
<Image HorizontalAlignment="Left" VerticalAlignment="Top" Source="/Vega;component/Assets/Icons/picture2.png" MaxWidth="150" MaxHeight="150" Name="image" />
<Button x:Name="btnSelected" Content="選擇" Margin="8,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Click="btnSelected_Click" />
</StackPanel>
</toolkit:BusyIndicator>
</UserControl>
點擊選擇按鈕,出現一個文件選擇框,然后將選中文件上傳到服務器,將返回的圖片路徑復制給Image控件:
{
var fileDialog = new OpenFileDialog()
{
Filter = "圖片(.jpg)|*.jpg|圖片(.jpeg)|*.jpeg|圖片(.png)|*.png",
Multiselect = false
};
var result = fileDialog.ShowDialog();
if (result.HasValue && result.Value)
{
var stream = fileDialog.File.OpenRead();
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
this.busyIndicator.IsBusy = true;
var op = this.uploadContext.Upload(bytes, fileDialog.File.Extension);
op.Completed += (opSender, opE) => {
this.ImageUri = new Uri(op.Value, UriKind.Relative);
this.busyIndicator.IsBusy = false;
};
}
}
然后,我為控件加了一個屬性,ImageUrl暴露給外面,提供綁定什么的功能。
"ImageUri",
typeof(Uri),
typeof(SelecteImage),
new PropertyMetadata(null , new PropertyChangedCallback(OnImageUriChanged)));
private static void OnImageUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((SelecteImage)d).OnImageUriChanged(e);
}
protected virtual void OnImageUriChanged(DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
return;
}
var uri = (Uri)e.NewValue;
if (!uri.IsAbsoluteUri)
{
//Application.Current.Host.Source
var source = Application.Current.Host.Source;
uri = new Uri(String.Format("{0}://{1}:{2}{3}",source.Scheme,source.Host,source.Port,uri.OriginalString), UriKind.Absolute);
}
this.image.Source = new BitmapImage(uri);
}
public Uri ImageUri
{
get { return (Uri)GetValue(ImageUriProperty); }
set { SetValue(ImageUriProperty, value); }
}
由于Silverlight中的相對Uri并不是只服務器上的資源,而是指XAP文件中的資源,所以我吧相對地址改為絕對地址。
6、DataForm,實現新增和編輯
<toolkit:DataForm Grid.Column="1" Margin="30,18,30,12" x:Name="dfCourse" AutoEdit="False" ItemsSource="{Binding ElementName=courseDomainDataSource, Path=Data}" CurrentItem="{Binding CurrentItem}" AutoGenerateFields="False" AutoCommit="False" EditEnded="dfCourse_EditEnded" CommandButtonsVisibility="Add, Edit, Commit, Cancel">
<toolkit:DataForm.Resources>
<DataTemplate x:Key="EditDataTemplate">
<StackPanel>
<toolkit:DataField Label="課程名稱:" Margin="0,0,0,8">
<TextBox Text="{Binding CourseName, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="標題:" Margin="0,0,0,8">
<TextBox Text="{Binding Title, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="子標題:" Margin="0,0,0,8">
<TextBox Text="{Binding SubTitle, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="短標題:" Margin="0,0,0,8">
<TextBox Text="{Binding ShortTitle, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="主講老師:" Margin="0,0,0,8">
<TextBox Text="{Binding Teachers, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="提供商:" Margin="0,0,0,8">
<TextBox Text="{Binding Provider, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="發布時間:" Margin="0,0,0,8">
<sdk:DatePicker SelectedDate="{Binding PublishDate, Mode=TwoWay}" />
</toolkit:DataField>
<toolkit:DataField Label="概要:" Margin="0,0,0,8">
<TextBox Text="{Binding Summary, Mode=TwoWay}" Height="120" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" AcceptsReturn="True" />
</toolkit:DataField>
<toolkit:DataField Label="圖片:" Margin="0,0,0,8">
<Vega_Controls:SelecteImage ImageUri="{Binding ThumbUrl, Mode=TwoWay}" Margin="0" d:LayoutOverrides="Width, Height"/>
</toolkit:DataField>
</StackPanel>
</DataTemplate>
</toolkit:DataForm.Resources>
<toolkit:DataForm.EditTemplate>
<StaticResource ResourceKey="EditDataTemplate"/>
</toolkit:DataForm.EditTemplate>
</toolkit:DataForm>
![](https://imageproxy.pixnet.cc/imgproxy?url=https://pic002.cnblogs.com/img/subwayline13/201009/2010090116334610.png)
{
if (e.EditAction == DataFormEditAction.Commit)
{
var course = (Course)this.courseDataGrid.SelectedItem;
if (course.CourseID == 0)
{
course.CreateTime = DateTime.Now;
}
this.courseDomainDataSource.SubmitChanges();
}
}
[Required(ErrorMessage="課程名稱不能為空")]
public string CourseName { get; set; }
![](https://imageproxy.pixnet.cc/imgproxy?url=https://pic002.cnblogs.com/img/subwayline13/201009/2010090116370157.png&width=619&height=551)