从LightSwitch Silverlight客户端中调用ASP.NET Web API

[原文发表地址] Calling ASP.NET Web API from a LightSwitch Silverlight Client

[原文发表时间] 2013-08-23 11:53 AM

       在我去塔浩湖度过一个美好的野营假期之前,我向大家展示了如何用你的LightSwitch中间层服务来使用WebAPI,以便于在数据库中调用存储过程。也说明了我们是如何用附加的SQL Server Data Tools (SSDT)来改善VisualStudio 2013中数据库管理的。如果你错过了下面的内容:

       你可以用SSDT做各种精彩的事情,当然用WebAPI也能办的到。启动Visual Studio 2012 Update 2 (LightSwitch V3), 我们赋予了它在中间层使用ServerApplicationContext的能力,以便于你可以利用LightSwitch内部所有的业务逻辑和数据逻辑来创建自定义的Web服务。这就使得重用你所想要的LightSwitch业务逻辑和数据投资扩展服务层变得非常容易。(看这里这里几个简单的例子)。

       我有几个关于我上一篇博客如何从LightSwitch HTML客户端中调用我们自己创建的Web API的问题。人们问怎样从LightSwitch Silverlight客户端中调用相同的Web API, 基于很多客户现如今都在使用桌面版的客户端,我今天会在这里展示一种可行的解决方案。尽管我今天在这里用到了它,但却不一定非要安装Visual Stusio 2013来完成– 用VS2012 Update2 或更高的版本也是可以的。

       因此,继续使用我们之前博文中的例子,让我们看看如何使Web API向LightSwitch Silverlight客户端返回一个结果。

编辑我们的Web API

       默认情况下,Web API将返回一个JSON格式的结果。这是一个非常棒的、轻量级的、用来交换数据的格式,它也一个是基于移动版jQuery 包括LightSwitch HTML客户端的跨平台标准。你也可以给Silverlight客户端返回一个JSON结果,然而你可能更希望用XML文件来工作。Web API的好处就是,发出web请求时只需要客户端在Accept头文件中指明“application/xml”,就将返回一个XML格式的结果。(另一方面讲,LightSwitch Odata services也将返回这些格式的结果,并且在LightSwitch HTML和Silverlight客户端的覆盖下使用JSON.)

       让我们对Web API控制器中的Get方法做一些小的修改,以便于可以返回一个易于我们序列化成XML的对象列表。首先从Server项目中向System.Runtime.Serialization添加一个引用,并在控制器类中导入命名空间。

image

       回想在数据库中调用存储过程的Get方法,它返回一个数据库中tables的列表,当然也包括了每一个table的行数。因此打开TableCountsController创建一个类并命名为TableInfo来表示这个数据。然后添加DataContract 和 DataMember属性,以便于序列化成我们所想要的。(请在MSDN上查看更多关于DataContracts的信息

VB:

 <DataContract(Namespace:="")>
Public Class TableInfo
    <DataMember>
    Property Name As String
    <DataMember>
    Property Count As Integer
End Class

C#:

 [DataContract(Namespace="")]
public class TableInfo
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]    
    public int Count { get; set; }
}

      现在我们可以调整粗体显示的代码来调用存储过程,这样就可以返回一个TableInfo对象的列表。这个更改将不会影响我们的JSON客户端。

VB:

 Public Class TableCountsController
    Inherits ApiController

    ' GET api/<controller>
    Public Function GetValues() As List(Of TableInfo)

        Dim reportResult As List(Of TableInfo) = Nothing

        Using context As ServerApplicationContext = ServerApplicationContext.CreateContext()

            'Only return this sensitive data if the logged in user has permission
            If context.Application.User.HasPermission(Permissions.SecurityAdministration) Then

                'The LightSwitch internal database connection string is stored in the 
                ' web.config as "_IntrinsicData". In order to get the name of external data 
                ' sources, use: context.DataWorkspace.*YourDataSourceName*.Details.Name
                Using conn As New SqlConnection(
                    ConfigurationManager.ConnectionStrings("_IntrinsicData").ConnectionString)

                    Dim cmd As New SqlCommand()
                    cmd.Connection = conn
                    cmd.CommandText = "uspGetTableCounts"
                    cmd.CommandType = CommandType.StoredProcedure
                    cmd.Connection.Open()
                    'Execute the reader into a new named type to be serialized
                    Using reader As SqlDataReader =
                        cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection)

                        reportResult = (From dr In reader.Cast(Of IDataRecord)()
                                        Select New TableInfo With {
                                            .Name = dr.GetString(0),
                                            .Count = dr.GetInt32(1)
                                        }
                                       ).ToList()

                    End Using
                End Using
            End If

            Return reportResult
        End Using
    End Function
End Class
 C#:
 public class TableCountsController : ApiController
{
    // GET api/<controller>
    public List<TableInfo> Get()
    {
        List<TableInfo> reportResult = null;

        using (ServerApplicationContext context = ServerApplicationContext.CreateContext())

            // Only return this sensitive data if the logged in user has permission
            if (context.Application.User.HasPermission(Permissions.SecurityAdministration))
            {
                {
                  //The LightSwitch internal database connection string is stored in the 
                  // web.config as "_IntrinsicData". In order to get the name of external data 
                  // sources, use: context.DataWorkspace.*YourDataSourceName*.Details.Name
                  using (SqlConnection conn =
                        new SqlConnection(ConfigurationManager.ConnectionStrings
                            ["_IntrinsicData"].ConnectionString))
                  {

                      SqlCommand cmd = new SqlCommand();
                      cmd.Connection = conn;
                      cmd.CommandText = "uspGetTableCounts";
                      cmd.CommandType = CommandType.StoredProcedure;
                      cmd.Connection.Open();
                      // Execute the reader into a new named type to be serialized
                      using (SqlDataReader reader =
                          cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
                      {
                          reportResult = reader.Cast<IDataRecord>()
                                         .Select(dr => new TableInfo 
                                          {
                                              Name = dr.GetString(0),
                                              Count = dr.GetInt32(1)
                                          }
                                          ).ToList();
                      }
                  }
                }
            }
        return reportResult;
    }
}

创建一个Custom Silverlight Control

       无论你用的是什么客户端,LightSwitch都会让你编辑你的自定义控件。如果你用的是HTML客户端,你写的就是自定义的JavaScriot code; 如果你用的是Silverlight客户端,你写的就是XAML文件。因此在LightSwitch解决方案中添加一个新的Silverlight类库。

image

       然后右键单击Silverlight Class Library,单击Add->New Item, 然后选择Silverlight User Control。按照你所希望用的XAML设计器来设计你的控件。我尽量用一个简单的例子来说明一下。下面我们编写代码来调用Web API,结果将显示在一个简单的DataGrid控件中。

 <UserControl
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk=https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk 
          x:Class="SilverlightClassLibrary1.SilverlightControl1"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
       <sdk:DataGrid Name="myGrid" IsReadOnly="True" AutoGenerateColumns="True" />
   
    </Grid>
</UserControl>

从Silverlight中调用Web API

       现在我们需要编写控件的代码了。用Silverlight可以指定浏览器或者客户端是否提供处理HTTP Web请求。在这个例子中我们将通过HttpWebRequest使用浏览器,以便于它能自动流畅我们的凭据,并且毫无干扰。(请看:如何处理浏览器或客户端的HTTP处理

       因此当你实例化控件时,你需要去调用WebRequest.RegisterPrefix 。(("https://", System.Net.Browser.WebRequestCreator.BrowserHttp)

       我们还需要给System.Xml.Serialization添加一个引用,并在控件类的最开始处导入它。用XMLSerializer来反序列化响应和填充DataGrid。我们用HttpWebRequest来发出请求。这就是我们指定的基于我们建立的Web API路径。(我在上一篇博客中说明过)

      (请注意Web API的URI与客户端是在同一域内的,Web API是在我们的LightSwitch服务器项目中托管的,这是作为和桌面客户端同一解决方案中的一部分。如果你想用这个代码来跨域访问,那么你需要用一个ClientAccessPolicy文件来实现。 更多信息请参照跨域边界所提供的服务。)

       下面是完整的用户控件代码。

VB:

 Imports System.Net
Imports System.Xml.Serialization
Imports System.IO

Public Class TableInfo
    Property Name As String
    Property Count As Integer
End Class
Partial Public Class SilverlightControl1
    Inherits UserControl

    Private TableCounts As List(Of TableInfo)

    Public Sub New()
        'Register BrowserHttp for these prefixes
        WebRequest.RegisterPrefix("https://",
                   System.Net.Browser.WebRequestCreator.BrowserHttp)
        WebRequest.RegisterPrefix("https://",
                   System.Net.Browser.WebRequestCreator.BrowserHttp)

        InitializeComponent()
        GetData()
    End Sub

    Private Sub GetData()
        'Construct the URI to our Web API
        Dim apiUri = New Uri(Application.Current.Host.Source, "/api/TableCounts/")
        'Make the request
        Dim request As HttpWebRequest = HttpWebRequest.Create(apiUri)
        request.Accept = "application/xml"
        request.BeginGetResponse(New AsyncCallback(AddressOf ProcessData), request)
    End Sub

    Private Sub ProcessData(ar As IAsyncResult)

        Dim request As HttpWebRequest = ar.AsyncState
        Dim response As HttpWebResponse = request.EndGetResponse(ar)

        'Deserialize the XML response
        Dim serializer As New XmlSerializer(GetType(List(Of TableInfo)))
        Using sr As New StreamReader(response.GetResponseStream(), 
                        System.Text.Encoding.UTF8)

            Me.TableCounts = serializer.Deserialize(sr)

            'Display the data back on the UI thread
            Dispatcher.BeginInvoke(Sub() myGrid.ItemsSource = Me.TableCounts)
        End Using

    End Sub
End Class
  

C#:

 using System.Xml.Serialization;
using System.IO;

namespace SilverlightClassLibrary1
{
    public class TableInfo
    {
        public string Name { get; set; }
        public int Count { get; set; }
    }
    public partial class SilverlightControl1 : UserControl
    {

        private List<TableInfo> TableCounts;

        public SilverlightControl1()
        {
            //Register BrowserHttp for these prefixes 
            WebRequest.RegisterPrefix("https://",
                       System.Net.Browser.WebRequestCreator.BrowserHttp);
            WebRequest.RegisterPrefix("https://",
                       System.Net.Browser.WebRequestCreator.BrowserHttp);


            InitializeComponent();
            GetData();
        }

        private void GetData()
        {
             //Construct the URI to our Web API
            Uri apiUri = new Uri(Application.Current.Host.Source, "/api/TableCounts/");

            //Make the request
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiUri);
            request.Accept = "application/xml";
            request.BeginGetResponse(new AsyncCallback(ProcessData), request);
        }

        private void ProcessData(IAsyncResult ar)
        {
             HttpWebRequest request = (HttpWebRequest)ar.AsyncState;
            HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);

            //Deserialize the XML response
            XmlSerializer serializer = new XmlSerializer(typeof(List<TableInfo>));
            using (StreamReader sr = new StreamReader(response.GetResponseStream(), 
                   System.Text.Encoding.UTF8))
            {
                this.TableCounts = (List<TableInfo>)serializer.Deserialize(sr);

                //Display the data back on the UI thread                
                Dispatcher.BeginInvoke(() => myGrid.ItemsSource = this.TableCounts);

            }
        }    
    }
} 

在LightSwitch中使用Custom Control

         最后我们需要在Silverlight客户端里的Silverlight screen中添加我们的自定义控件。确保我们首先应该重新编译解决方案, 然后再添加screen,除了New和Details外随意选择一个screen模板,并且不要选择任何Screen Data。

image

        在screen的内容索引中添加一个新的自定义控件。

 

image

       然后给你的Silverlight类库中添加一个解决方案引用,选择上边编译过的自定义控件(如果该控件没有显示出来,请重新编译你的解决方案。)

image

        最后,在属性窗口中我将会把控件命名成“TableCounts”,把标签的位置设为“置顶”,把“水平垂直”设为“拉伸”,这样看起来会好看一些。

        F5编译并运行该解决方案(为了确保你具有管理员权限,你可以通过选择“访问控制选项卡”上的“授权调试”来获取安全管理员权限)你应该能看到从存储过程中返回数据,而且它也会显示在网格中。

image

 

       如果你运行的是我们上一篇博客中编译的HTML客户端,那么你会发现运行结果和以前没有任何区别,返回的JOSN也与以前的形状一样。

image

结束

        在最后的几篇博客中我已经带大家浏览了很多LightSwitch的高级功能,LightSwitch拥有非常丰富的扩展模型,你可以用他们来自定义比原来更为丰富的应用程序。当你需要向LightSwitch程序提供自定义功能时,将会有很多选项。

        用LightSwitch使用Web API能够让你灵活地创建自定义的web方法,这样你就可以通过ServerApplicationContext来利用LightSwitch中间层所有的数据和业务逻辑。如果你安装了LightSwitch3或者更高的版本(VS2012 Update 2以上版本或者VS2013),那么你现在就可以开始了。

       获取更多关于用LightSwitch使用Web API的信息请看:

       更多关于在Visual Studio 2013中的LightSwitch使用数据库项目的信息,请看:

       慢慢享受这些吧!