Friday, November 06, 2009

ASP.NET Silverlight 3 Web Service integration DRAFT


Development Environment

Dell Laptop

.Net Framework 3.5 SP1

Visual Studio.Net 2008 SP1 (Team System Developer, Tester, Database)

Silverlight 3.0

Fiddler 2

SQL Server Management Studio 2008 SP1

Alpha/Dev Web Server

Windows Server 2003 Standard Edition SP2

           
IIS v 6.0

Alpha/Dev SQL Server 2005 - databases (2) are 2000 compatible library

Existing Production Application

“Transcript Currier”

ASP.NET 2.0 (VB) Web Application

           
SQL Server 2000

The existing system:

User Type: Customer

1.     
"Login" Page - customer logs in

2.     
"Jobs List" Page - customer chooses a job or creates a
new one

3.     
"Customer Upload" page

a.      
Fill out the basic job information

b.     
Identify the number of transcripts within the job
(GridView Control)


                                                  
i.     
Transcript Header (auto-generated <JobNumber>An)


                                                
ii.     
Case Number, Description, Copies (user input)

c.      
Pick a transcript header (dropdown control) and
associate a file (file upload control) - 10 static control sets

d.     
Process the Job (click button control)


                                                  
i.     
validation run

1.     
required fields

2.     
valid business dates

3.     
check file type (usually audio)


                                                
ii.     
moves the files to File Server via virtual directory on
Web Server IIS Site


                                               
iii.     
compresses files into a <project code directory>/<job
number directory>\<job number>.zip file


                                              
iv.     
processes the job details in database posting compressed
file path for transcriptionists to pick up work

The desire is to modify the existing application to

1.     
replace 10 static control sets with one control set
(transcription header dropdown and file picker)

2.     
allow the user to select multiple files from various
location

3.     
present a list of properly associated files <job number>
<transcript header> and <files>

4.     
 compress files or
take a compressed file <job number>.zip {<transcript header directory>\<fiels>}

5.     
transfer compressed file to the File Server via virtual
directory on Web Server IIS site (as is done now)

The Silverlight User Control as I have it outlined now is a replacement for the
static dropdown/file upload control set and contains a ComboBox, a filepicker,
two textblocks, and a button.


·       
ComboBox – select the transcript header
aka CaseFileName


·       
FilePicker – choose the file(s) from your machine
(may be done repeatedly)


·       
TextBlock1 – display the files picked


·       
TextBlock2 – display the files existing/selected
for the transcript


·       
Button – Upload the files

In the current outlined work flow, when the user is done they’ll process the job
details

I’ve got four issues when I actually tie the tools together at this moment.

  1. Silverlight ComboBox does not populate
  2. Silverlight intermittently spits up
    cross-domain policy soap errors
  3. Silverlight upload file(s) – splits file(s) into small
    packages and moves to the file server – appears to be spawning multiple threads
    resulting in file write conflict errors
  4. The UI looks atrociously worse than it did before – which is
    saying a lot.

So as always, I’ve decoupled the scenarios to try to get to the base issues.

  1. Silverlight ComboBox does not populate

http://10.1.251.62/ys2005web/TestSilverlightControl1.aspx

http://10.1.251.62/ys2005web/SlC1WebService.asmx

SlC1WebService




Click here for a complete list of operations.

GetTranscriptHeaderRecords


Test


The test form is only available for requests from the local machine.

SOAP 1.1

The following is a sample SOAP 1.1 request and response. The placeholders shown need to be replaced with actual values.


POST /ys2005web/SlC1WebService.asmx HTTP/1.1
Host: 10.1.251.62
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://10.1.251.62/ys2005web/GetTranscriptHeaderRecords"
 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetTranscriptHeaderRecords xmlns="http://10.1.251.62/ys2005web/">
      <jobNumber>int</jobNumber>
    </GetTranscriptHeaderRecords>
  </soap:Body>
</soap:Envelope>

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetTranscriptHeaderRecordsResponse xmlns="http://10.1.251.62/ys2005web/">
      <GetTranscriptHeaderRecordsResult>
        <vwTranscriptHeaderRecords>
          <CaseNo>string</CaseNo>
          <Copies>string</Copies>
          <Description>string</Description>
          <Dumped>boolean</Dumped>
          <Filename>string</Filename>
          <JobNumber>int</JobNumber>
        </vwTranscriptHeaderRecords>
        <vwTranscriptHeaderRecords>
          <CaseNo>string</CaseNo>
          <Copies>string</Copies>
          <Description>string</Description>
          <Dumped>boolean</Dumped>
          <Filename>string</Filename>
          <JobNumber>int</JobNumber>
        </vwTranscriptHeaderRecords>
      </GetTranscriptHeaderRecordsResult>
    </GetTranscriptHeaderRecordsResponse>
  </soap:Body>
</soap:Envelope>



SOAP 1.2

The following is a sample SOAP 1.2 request and response. The placeholders shown need to be replaced with actual values.

POST /ys2005web/SlC1WebService.asmx HTTP/1.1
Host: 10.1.251.62
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
 
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetTranscriptHeaderRecords xmlns="http://10.1.251.62/ys2005web/">
      <jobNumber>int</jobNumber>
    </GetTranscriptHeaderRecords>
  </soap12:Body>
</soap12:Envelope>

HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
 
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetTranscriptHeaderRecordsResponse xmlns="http://10.1.251.62/ys2005web/">
      <GetTranscriptHeaderRecordsResult>
        <vwTranscriptHeaderRecords>
          <CaseNo>string</CaseNo>
          <Copies>string</Copies>
          <Description>string</Description>
          <Dumped>boolean</Dumped>
          <Filename>string</Filename>
          <JobNumber>int</JobNumber>
        </vwTranscriptHeaderRecords>
        <vwTranscriptHeaderRecords>
          <CaseNo>string</CaseNo>
          <Copies>string</Copies>
          <Description>string</Description>
          <Dumped>boolean</Dumped>
          <Filename>string</Filename>
          <JobNumber>int</JobNumber>
        </vwTranscriptHeaderRecords>
      </GetTranscriptHeaderRecordsResult>
    </GetTranscriptHeaderRecordsResponse>
  </soap12:Body>
</soap12:Envelope>



B. The Silverlight Control


Imports

System.Diagnostics


Imports

System.Windows.Browser


Partial

Public Class
SilverlightControl1


   
Inherits UserControl


   
Public Sub
New()


       
InitializeComponent()


   
End Sub


   
Public Sub
New(ByVal
JobNumber As String)


       
InitializeComponent()


       
cboTranscriptHeader_Load(JobNumber)


   
End Sub


   
'
-------------------------------------------------------------------------------------


#Region

"** event handling"


   
Protected Sub
cboTranscriptHeader_ItemSelected( _


       
ByVal sender As
Object, _


       
ByVal e As
SelectionChangedEventArgs) Handles
cboTranscriptHeader.SelectionChanged


       
Debug.Assert(sender IsNot
Nothing, "sender is
null."
)


       
Debug.Assert(e IsNot
Nothing
, "e is null.")


       
If cboTranscriptHeader.SelectedItem
Is Nothing
Then


           
Return


       
End If


       
'Note: "Transcript Header Id"
(WebDumper.dbo.CustomerUploadFiles.CaseFileName)


       
Dim TranscriptHeader As
String =
Convert.ToString(cboTranscriptHeader.SelectedItem)


       
'ToDo: Code required when a Transcript Header Id is
selected


       
'   A
specific Customer Job have one Job Header Record (DB: WebDumper TBL:
CustomerUploadHeader)


       
'  
Each Customer Job has one or more Transcript Header Records (DB: WebDumper TBL:
CustomerUploadFiles)


       
'  
Each set of uploaded files should reside in a direcory named after the
Transcript Header Id


       
'  
All the Transcript Header Id Directories should be complessed into a single file
named by the "Job Number" 


       
'  
Direcory Structure:


       
'      
"Project Code" (dir)


       
'      
-->  "Job Number" (dir)


       
'      
-->  -->  "Job Number".ZIP (compressed file)


       
'      
-->  -->
 --> 
"Transcript Header Id" (dir)


       
'      
-->  -->  --> 
-->  *.* (audio and text
files)


   
End Sub


   
Public Function
BuildAbsoluteUri(ByVal relativeUri
As String)
As Uri


       
' Get current absolute Uri; this depends on where the
app is deployed


       
Dim uri1 As Uri
= System.Windows.Browser.HtmlPage.Document.DocumentUri


       
Dim uriString As
String = uri1.AbsoluteUri


       
' Replace page name with relative service Uri


       
Dim ls As Int32
= uriString.LastIndexOf("/"c)


       
uriString = uriString.Substring(0, ls + 1) + relativeUri


       
' Return new Uri


       
Return New
Uri(uriString, UriKind.Absolute)


   
End Function


   
Private _jobNumber As
Nullable(Of Integer)


   
Public Property
jobNumber() As Nullable(Of Integer)


        Get


           
Return _jobNumber


       
End Get


       
Set(ByVal value
As Nullable(Of
Integer))


           
_jobNumber = value


       
End Set


   
End Property


   
Private Sub
MainPage_Loaded(ByVal sender
As Object,
ByVal e As
System.Windows.RoutedEventArgs) Handles
Me.Loaded


       
Debug.Assert(sender IsNot
Nothing, "sender is
null."
)


       
Debug.Assert(e IsNot
Nothing
, "e is null.")


       
HtmlPage.RegisterScriptableObject("SilverlightComponentOne",
Me)


       
'' 
Scriptable Member accessible by JavaScript/Ajax and Silverlight interface


       
''  
loadtranscriptheaderdropdownlist(jobNumber)


   
End Sub


    <ScriptableMember()> _


   
Sub cboTranscriptHeader_Load(ByVal jobnumber As
String)


       
'ToDo: A cleaner way to go from String to Nullable
Integer?


       
Dim _value As
Integer


       
If Integer.TryParse(jobnumber,
_value) Then


           
_jobNumber = _value


       
Else


           
_jobNumber = Nothing


       
End If


       
Dim ws As
SilverlightApplicationUploader.SLC1WebService.SlC1WebServiceSoapClient = _


          
New
SilverlightApplicationUploader.SLC1WebService.SlC1WebServiceSoapClient()


       
Dim request As
New
SLC1WebService.GetTranscriptHeaderRecordsRequest(Me.jobNumber)


       
AddHandler
ws.GetTranscriptHeaderRecordsCompleted, AddressOf
ws_GetTranscriptHeaderRecords


       
ws.GetTranscriptHeaderRecordsAsync(request)


   
End Sub


   
Private Sub
ws_GetTranscriptHeaderRecords(ByVal sender
As Object, _


       
ByVal e As
SLC1WebService.GetTranscriptHeaderRecordsCompletedEventArgs)


       
Me.cboTranscriptHeader.Items.Clear()


       
For Each record
As
SilverlightApplicationUploader.SLC1WebService.vwTranscriptHeaderRecords
In e.Result.GetTranscriptHeaderRecordsResult


           
Me.cboTranscriptHeader.Items.Add(record.Filename)


       
Next


   
End Sub


#End

Region '"** event
handling"


   
''
-------------------------------------------------------------------------------------


End

Class

C. The client config


<?
xml
version="1.0"
encoding="utf-8" ?>


<
configuration>


  <
system.serviceModel>


    <
bindings>


      <
basicHttpBinding>


       
<
binding

name="SlC1WebServiceSoap" closeTimeout="00:01:00" openTimeout="00:01:00"


           

receiveTimeout="00:10:00"
sendTimeout="00:01:00"


           

maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647"


           

textEncoding="utf-8" >


         
<
security

mode="None"/>


       
</
binding>


      </
basicHttpBinding>


    </
bindings>


    <
client>


      <
endpoint

address="http://localhost:2443/SlC1WebService.asmx"


         

binding="basicHttpBinding"
bindingConfiguration="SlC1WebServiceSoap"


           

contract="SLC1WebService.SlC1WebServiceSoap" name="SlC1WebServiceSoap" />


    </
client>


  </
system.serviceModel>


</
configuration>

D. The Silverlight Control xaml


<
UserControl x:Class="SilverlightApplicationUploader.SilverlightControl1"


  
 xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


  
 xmlns
:x="http://schemas.microsoft.com/winfx/2006/xaml"


           
 xmlns:data="clr-namespace:SilverlightApplicationUploader.SLC1WebService"


  
 Width
="400" Height="300">

   
<UserControl.Resources>

       


<
Style x:Key="ControlBorder"
TargetType
="Border">

           


<
Setter Property="BorderThickness"
Value
="3" />

           


<
Setter Property="Background"
Value
="#FFC2F5CB" />

           


<
Setter Property="BorderBrush"
Value
="#FF257004" />

           


<
Setter Property="CornerRadius"
Value
="6" />

           


<
Setter Property="Margin"
Value
="5" />

           


<
Setter Property="Padding"
Value
="5" />

       


</
Style>

       


<
Style x:Key="ControlTitle"
TargetType
="TextBlock">

           


<
Setter Property="FontSize"
Value
="14" />

           


<
Setter Property="HorizontalAlignment"
Value
="Center" />

           


<
Setter Property="FontFamily"
Value
="Courier New" />

           


<
Setter Property="FontWeight"
Value
="Bold" />

           


<
Setter Property="Foreground"
Value
="#FF1F6900" />

       


</
Style>

   
</UserControl.Resources>

   
<Grid x:Name="LayoutRoot"
Background
="White">

       


<
ScrollViewer>

           


<
StackPanel x:Name="_mainPanel"
Margin
="6" >

               


<
Border Style="{StaticResource ControlBorder}">

                   


<
StackPanel>

                       


<
TextBlock x:Name="DisplayTranscriptHeader"
Text
="TranscriptHeader" Style="{StaticResource ControlTitle}" />

                        <StackPanel Orientation="Horizontal"
Height
="50">

                           


<
ComboBox x:Name="cboTranscriptHeader"
Width
="80" Height="30"


                                    
 VerticalAlignment="Top" Margin="5"


                      
              
 SelectionChanged="cboTranscriptHeader_ItemSelected"


                                    
 ItemsSource="{Binding Mode=OneWay}" >

                               


<
ComboBoxItem Content="CaseFileNumber" />

                           


</
ComboBox>

                       


</
StackPanel>

                   


</
StackPanel>

               


</
Border>

           


</
StackPanel>

       


</
ScrollViewer>

   
</Grid>


</
UserControl>

E. The Application Class


Partial

Public Class App


   
Inherits Application


    public
Sub New()


       
InitializeComponent()


   
End Sub


   
Private Sub
Application_Startup(ByVal o
As Object,
ByVal e As
StartupEventArgs) Handles
Me.Startup


       
Dim JobNumber As
String = e.InitParams("JobNumber")


       
Me.RootVisual = New
SilverlightControl1(JobNumber)


   
End Sub


   
Private Sub
Application_Exit(ByVal o
As Object,
ByVal e As
EventArgs) Handles Me.Exit


   
End Sub


   
Private Sub
Application_UnhandledException(ByVal sender
As object,
ByVal e As
ApplicationUnhandledExceptionEventArgs) Handles
Me.UnhandledException


       
' If the app is running outside of the debugger then
report the exception using


       
' the browser's exception mechanism. On IE this will
display it a yellow alert


       
' icon in the status bar and Firefox will display a
script error.


       
If Not
System.Diagnostics.Debugger.IsAttached Then


           
' NOTE: This will allow the application to
continue running after an exception has been thrown


           
' but not handled.


        
   
' For production applications this error
handling should be replaced with something that will


           
' report the error to the website and stop the
application.


           
e.Handled = True


           
Deployment.Current.Dispatcher.BeginInvoke(New
Action(Of
ApplicationUnhandledExceptionEventArgs)(AddressOf
ReportErrorToDOM), e)


       
End If


   
End Sub


  
Private
Sub ReportErrorToDOM(ByVal e As
ApplicationUnhandledExceptionEventArgs)


       
Try


           
Dim errorMsg As
String = e.ExceptionObject.Message +
e.ExceptionObject.StackTrace


           
errorMsg = errorMsg.Replace(""""c,
"'"c).Replace(ChrW(13) & ChrW(10), "\n")


           
System.Windows.Browser.HtmlPage.Window.Eval("throw
new Error(""Unhandled Error in Silverlight Application "
+ errorMsg +
""");")


       
Catch


       
End Try


   
End Sub


End

Class

F. The Application xaml


<
Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


           
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


           
 x:Class="SilverlightApplicationUploader.App"


           
 >

   
<Application.Resources>

       

   
</Application.Resources>


</
Application>

G. The client access policy


<?
xml
version="1.0"
encoding="utf-8" ?>


<
access-policy>


  <
cross-domain-access>


    <
policy>


      <
allow-from

http-request-headers="*">


       
<!--
/*Specify
request headers like Content-Type,SOAPAction*/
-->


       
<
domain

uri="*"/>


      </
allow-from>


      <
grant-to>


       
<
resource

include-subpaths="true"
path="/"/>


      </
grant-to>


    </
policy>


  </
cross-domain-access>

</access-policy>

 

H. The cross domain policy


<?
xml
version="1.0"
encoding="utf-8" ?>


<
cross-domain-policy>


  <
allow-http-request-headers-from

domain="*" headers="*"/>

</cross-domain-policy>

I. The Web Service


<System.Web.Services.WebService(Namespace:="http://localhost:2443/")>
_


<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)>
_


<ToolboxItem(False)> _


Public

Class SlC1WebService


   
Inherits System.Web.Services.WebService


    <WebMethod()> _


    
Public Function
GetTranscriptHeaderRecords(ByVal jobNumber
As Global.System.Nullable(Of Integer))
As List(Of
vwTranscriptHeaderRecords)


       
'ToDo: Call this method from SL passing JobNumber and
Retreiving list of transcriptHeaders

 


       
Dim transcriptHeaders
As
New List(Of
vwTranscriptHeaderRecords)


 


       
Dim de As
New StenoMainEntities


 
       
' Execute the query and get the
ObjectQueryResult.


       
Dim TranscriptHeaderRecordsResult
As Objects.ObjectResult(Of vwTranscriptHeaderRecords) =
de.GetTranscriptHeaderRecords(jobNumber)


       
' Iterate through the collection of Product items.


       
Dim result As
vwTranscriptHeaderRecords


       
For Each result
In TranscriptHeaderRecordsResult


           
transcriptHeaders.Add(result)


       
Next


       
Return transcriptHeaders

   
End Function


End

Class


 
'''
<summary>

''' Parameter class

'''
</summary>


Public

Class Parameter


   
Private _Key As
String


   
Public Property
Key() As String


       
Get


           
Return _Key


       
End Get


       
Set(ByVal value
As String)


           
_Key = value


       
End Set


   
End Property


   
Private _Value As
String


   
Public Property
Value() As String


       
Get


           
Return _Value


       
End Get


       
Set(ByVal value
As String)


           
_Value = value


       
End Set


   
End Property


End

Class

 A direct call of the data transcript function works - - - 


Public

Partial Class
TestGetTranscriptHeader


   
Inherits System.Web.UI.Page


   
Protected Sub
Page_Load(ByVal sender
As
Object, ByVal
e As System.EventArgs)
Handles
Me.Load


       
Dim jobNumber As
Nullable(Of Integer)
= 520124


       
Dim transcriptHeaders
As
New List(Of
vwTranscriptHeaderRecords)


       
Dim de As
New DBEntities


       
' Execute the query and get the ObjectQueryResult.


       
Dim TranscriptHeaderRecordsResult
As Objects.ObjectResult(Of vwTranscriptHeaderRecords) =
de.GetTranscriptHeaderRecords(jobNumber)


       
' Iterate through the collection of Product items.


       
Dim result As
vwTranscriptHeaderRecords


       
For Each result
In TranscriptHeaderRecordsResult


           
transcriptHeaders.Add(result)


       
Next


       
Response.Write("Results" & vbCrLf)


       
For Each result
In transcriptHeaders


           
Response.Write("-" & result.JobNumber
& ", " & result.Filename & vbCrLf)


       
Next


   
End Sub

End Class