天天看點

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯

  建立一個Silverlight Business Application,首先修改web.config,他自動生成的配置比較省略,我手動加入membership,role,profile的配置,我是ASP.NET MVC的項目中拷貝過來,直接用他的配置也可以,不過手動配置一下連接配接字元串LocalSqlServer,不然不能運作。

<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>

<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />

<properties>

<add name="FriendlyName" />

</properties>

</profile>

<roleManager enabled="true">

<add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/Vega" />

<add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />

</roleManager>

  我用的連接配接字元串名字是ApplicationServices,如果沒安裝ASP.NET的SQL資料,可以用Visual Studio 指令提示(2010) (在開始菜單的Visual Studio Tools裡)調出指令提示符輸入aspnet_regsql,調出“ASP.NET SQL SERVER安裝向導”,而在運作-cmd裡因為沒有相關環境變量,是調不出安裝向導的。

  建立一個ADO.NET實體資料模型。

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯

  編譯一下,然後建立Domain Service Class,勾上Course,如果不編譯,那麼資料實體模型是找不到的。我們需要Microsoft Silverlight 4 Toolkit,http://silverlight.codeplex.com/,沒安裝的話要先安裝。

  使用者登入後往往要根據不用角色實作不同的功能導航,是以要修改/Views/Login/LoginStatus.xaml 這個登入狀态的使用者控件。首先加上管理者的功能面闆。

<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"/>

<TextBlock Text=" | " Style="{StaticResource SpacerStyle}"/>

<HyperlinkButton TargetName="ContentFrame" Content="課程" VerticalAlignment="Center" Foreground="White" NavigateUri="/Courses"/>

<HyperlinkButton TargetName="ContentFrame" Content="學員" VerticalAlignment="Center" Foreground="White" NavigateUri="/Students"/>

<HyperlinkButton TargetName="ContentFrame" Content="排課" VerticalAlignment="Center" Foreground="White" NavigateUri="/Schedules"/>

<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)">

</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角色和使用者。

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯

  好了,使用者程式登入之後判斷有admin角色,登入後就顯示管理者的功能導航了。

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯

  建立一個課程頁面,在Page頁面上加上DomainDataSource,一種辦法是通過資料源視窗直接拽頁面上去。

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯

  他會自動幫你建立一個riaDataSource,還有DataGrid。

  在DataGrid外面加一個BusyIndicator(他是Silverlight Toolkit的控件之一),如果BusyIndicator得IsBusy為Ture,那麼會出現一個等待的提示,并且BusyIndicator包含的内容不可操作,是以要把BusyIndicator的IsBusy和DomainDataSource的IsBusy綁定起來: IsBusy="{Binding IsBusy, ElementName=courseDomainDataSource}" 。

  DataGrid已經和DomainDataSource綁定了,IDE幫我們自動加上代碼: ItemsSource="{Binding Data, ElementName=courseDomainDataSource}"。

  我還要加個簡單搜尋,通過課程名稱模糊搜尋,加一個TextBox。

<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)

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}" />

<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);

  效果如下:

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯

  隻需要逐個删除清單中的對象,然後DomainDataSource.SubmitChanges()就行了,很友善吧。

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();

  我需要一個上傳圖檔的功能,首先需要背景支援,建一個DomainService專門負責上傳。

[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" />

</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檔案中的資源,是以我吧相對位址改為絕對位址。

  這個圖檔上傳控件就OK樂。

  首先拖上去一個DataForm控件,然後将DataForm和DomainService綁定起來。ItemsSource="{Binding ElementName=courseDomainDataSource, Path=Data}" CurrentItem="{Binding ElementName=courseDomainDataSource, Path=DataCurrentItem}",ItemSource綁定的是資料源,CurrentItem綁定時目前項目,如果不綁定ItemSource,隻綁定CurrentItem的話就可以用CurrentItem="{Binding ElementName=courseDomainDataSource, Path=Data.CurrentItem}",不過如果這樣的話,就隻能編輯不能新增了。DataForm有很多按鈕,比如導航的,編輯的,删除的,我隻需需要增加和編輯,CommandButtonsVisibility="Add, Edit, Commit, Cancel",如果需要全部按鈕的話CommandButtonsVisibility="All"就行了。

  另外,外面要重新設定DataForm的字段,把剛才做的上傳圖檔的控件加進入,我需要編輯EditDataTemplate。

<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 Label="子标題:" Margin="0,0,0,8">

<TextBox Text="{Binding SubTitle, Mode=TwoWay}" />

<toolkit:DataField Label="短标題:" Margin="0,0,0,8">

<TextBox Text="{Binding ShortTitle, Mode=TwoWay}" />

<toolkit:DataField Label="主講老師:" Margin="0,0,0,8">

<TextBox Text="{Binding Teachers, Mode=TwoWay}" />

<toolkit:DataField Label="提供商:" Margin="0,0,0,8">

<TextBox Text="{Binding Provider, Mode=TwoWay}" />

<toolkit:DataField Label="釋出時間:" Margin="0,0,0,8">

<sdk:DatePicker SelectedDate="{Binding PublishDate, Mode=TwoWay}" />

<toolkit:DataField Label="概要:" Margin="0,0,0,8">

<TextBox Text="{Binding Summary, Mode=TwoWay}" Height="120" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" AcceptsReturn="True" />

<toolkit:DataField Label="圖檔:" Margin="0,0,0,8">

<Vega_Controls:SelecteImage ImageUri="{Binding ThumbUrl, Mode=TwoWay}" Margin="0" d:LayoutOverrides="Width, Height"/>

</toolkit:DataForm.Resources>

<toolkit:DataForm.EditTemplate>

<StaticResource ResourceKey="EditDataTemplate"/>

</toolkit:DataForm.EditTemplate>

</toolkit:DataForm>

把SelectImage的ImageUrl屬性和實體的屬性綁定起來。

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、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;

  最後最後,加上驗證。直接修改DomainService.matadata.cs和ASP.NET MVC2內建的驗證方式一樣,使用Attribute的方式:

[Display(Name="課程名稱")]

[Required(ErrorMessage="課程名稱不能為空")]

public string CourseName { get; set; }

  不過效果好多了

一起談.NET技術,Silverlight + RIA Service的SUID的執行個體  1、準備工作  2、使用者登入和角色  3、綁定資料源  4、删除全部選擇的項目。   5、上傳圖檔  6、DataForm,實作新增和編輯