1、准备工作
< membership > < 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、用户登录和角色
< StackPanel x:Name ="adminControls" Style =" {StaticResource LoginPanelStyle} " > <!-- 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的代码:
< vsm:VisualState x:Name ="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的绑定:
public LoginStatus() { 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、绑定数据源
< 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" />
好了,然后拖一个DataPaper上去实现分页,也和DomainDataSource绑定上 Source="{Binding Data, ElementName=courseDomainDataSource}"。
我们还要加上一排CheckBox用于多选,跟ASP.NET的GridView差不多,有一种DataGridTemplateColumn。CheckBox直接绑定实体对象,加上Tag="{Binding}",然后给CheckBox加上事件,在选中的把绑定的对象加入到一个List里,取消选中的时候在取出来,维护一个要删除对象的列表。
private IList < Course > deletedCourses = new List < Course > ();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的全部代码:
< toolkit:BusyIndicator x:Name ="dataBusyIndicator" BusyContent ="载入课程数据" IsBusy =" {Binding IsBusy, ElementName=courseDomainDataSource} " > < 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 AutoLoad ="True" Height ="0" LoadedData ="courseDomainDataSource_LoadedData" x:Name ="courseDomainDataSource" QueryName ="GetCoursesQuery" Width ="0" > < 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,也可以在后台加一个默认排序:
public IQueryable < Course > GetCourses() { return this .ObjectContext.Courses.OrderByDescending < Course, int > (c => c.CourseID); }
效果如下:
4、删除全部选择的项目。
private
void
button1_Click(
object
sender, RoutedEventArgs e)
{
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、上传图片
[EnableClientAccess()] 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建一个用户控件,专门负责上传和现实图片。
< UserControl xmlns:toolkit ="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit" x:Class ="Vega.Controls.SelecteImage" 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控件:
private void btnSelected_Click( object sender, RoutedEventArgs e) { 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暴露给外面,提供绑定什么的功能。
public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register( " 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:BusyIndicator Grid.Column ="1" x:Name ="formBusyIndicator" IsBusy =" {Binding IsBusy, ElementName=courseDomainDataSource} " /> < 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 >
private void dfCourse_EditEnded( object sender, DataFormEditEndedEventArgs e) { if (e.EditAction == DataFormEditAction.Commit) { var course = (Course) this .courseDataGrid.SelectedItem; if (course.CourseID == 0 ) { course.CreateTime = DateTime.Now; } this .courseDomainDataSource.SubmitChanges(); } }
[Display(Name = " 课程名称 " )] [Required(ErrorMessage = " 课程名称不能为空 " )] public string CourseName { get ; set ; }