Importing data into a Microsoft Visual Studio LightSwitch application can be a bit tricky, especially if we are talking about a LightSwitch web application. Since I’ve heard several people ask how to import data using a web application, I figured now would be a good time to blog about this.
I’m going to teach you some tips and tricks on importing data into a web application. It can get tricky in this scenario because web applications typically have additional security issues you need to deal with. But I think you’ll find once we are done, that there is very little code, and it’s easy to understand.
Some other LightSwitch people have already blogged about importing data into a LightSwitch application (e.g. Dan Seefeldt and the LightSwitch team). These are some awesome posts for the basics of importing and exporting. But for our slightly more specialized scenario we want to:
The real issue we need to work around is the fact that we have a web application and NOT a desktop application. Because of that we are going to have to do a couple of extra steps to make this work.
Let’s get started!
Because I’m lazy, we are going to make another application to manage “Contacts” for this example (like we did for the last blog post).
Now comes the unusual part of this example. We are going to add our own Silverlight dialog to our LightSwitch application. The “Import Data” button is going to invoke this Silverlight dialog. (I am not the first to go adding Silverlight dialogs to a LightSwitch application – see Eric Erhardt’s blog post on this.)
I used Visual Studio Ultimate to make my own very simple Silverlight dialog which we are going to import into our Visual Studio LightSwitch project. The Silverlight dialog contains 4 controls:
Let’s add the dialog now, and then I’ll give the technical reasons for why we are doing it this way.
<controls:ChildWindow x:Class="LightSwitchApplication.UserCode.SelectFileWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Width="394" Height="305" Title="Select File Dialog" > <Grid x:Name="LayoutRoot" Margin="2"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" /> <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" /> <Button Content="Browse" Height="23" HorizontalAlignment="Left" Margin="291,92,0,0" Name="BrowseButton" VerticalAlignment="Top" Width="75" Click="BrowseButton_Click" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="66,92,0,0" Name="FileTextBox" VerticalAlignment="Top" Width="219" IsEnabled="True"/> </Grid> </controls:ChildWindow>
<controls:ChildWindow x:Class="LightSwitchApplication.UserCode.SelectFileWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="394" Height="305"
Title="Select File Dialog" >
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
<Button Content="Browse" Height="23" HorizontalAlignment="Left" Margin="291,92,0,0" Name="BrowseButton" VerticalAlignment="Top" Width="75" Click="BrowseButton_Click" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="66,92,0,0" Name="FileTextBox" VerticalAlignment="Top" Width="219" IsEnabled="True"/>
</Grid>
</controls:ChildWindow>
//' Copyright © Microsoft Corporation. All Rights Reserved. //' This code released under the terms of the //' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html) using System; using System.IO; using System.Windows; using System.Windows.Controls; namespace LightSwitchApplication.UserCode { public partial class SelectFileWindow : ChildWindow { public SelectFileWindow() { InitializeComponent(); } private FileStream documentStream; public FileStream DocumentStream { get { return documentStream; } set { documentStream = value; } } /// <summary> /// OK Button /// </summary> private void OKButton_Click(object sender, RoutedEventArgs e) { this.DialogResult = true; } /// <summary> /// Cancel button /// </summary> private void CancelButton_Click(object sender, RoutedEventArgs e) { this.DialogResult = false; } /// <summary> /// Browse button /// </summary> private void BrowseButton_Click(object sender, RoutedEventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); // Limit the dialog to only show ".csv" files, // modify this as necessary to allow other file types openFileDialog.Filter = "csv files (*.csv)|*.csv"; openFileDialog.FilterIndex = 1; if (openFileDialog.ShowDialog() == true) { this.FileTextBox.Text = openFileDialog.File.Name; this.FileTextBox.IsReadOnly = true; System.IO.FileStream myStream = openFileDialog.File.OpenRead(); documentStream = myStream; } } } }
//' Copyright © Microsoft Corporation. All Rights Reserved.
//' This code released under the terms of the
//' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html)
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
namespace LightSwitchApplication.UserCode
{
public partial class SelectFileWindow : ChildWindow
public SelectFileWindow()
InitializeComponent();
}
private FileStream documentStream;
public FileStream DocumentStream
get { return documentStream; }
set { documentStream = value; }
/// <summary>
/// OK Button
/// </summary>
private void OKButton_Click(object sender, RoutedEventArgs e)
this.DialogResult = true;
/// Cancel button
private void CancelButton_Click(object sender, RoutedEventArgs e)
this.DialogResult = false;
/// Browse button
private void BrowseButton_Click(object sender, RoutedEventArgs e)
OpenFileDialog openFileDialog = new OpenFileDialog();
// Limit the dialog to only show ".csv" files,
// modify this as necessary to allow other file types
openFileDialog.Filter = "csv files (*.csv)|*.csv";
openFileDialog.FilterIndex = 1;
if (openFileDialog.ShowDialog() == true)
this.FileTextBox.Text = openFileDialog.File.Name;
this.FileTextBox.IsReadOnly = true;
System.IO.FileStream myStream = openFileDialog.File.OpenRead();
documentStream = myStream;
For the sake of modularity and encapsulation we will create a separate class to handle the parsing of the .csv file.
//' Copyright © Microsoft Corporation. All Rights Reserved. //' This code released under the terms of the //' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html) using System; using System.Collections.Generic; using System.IO; namespace LightSwitchApplication.UserCode { public class ImportDataFile { /// <summary> /// Import data from a comma delimited stream /// And using the workspace that was passed in, insert the imported data /// so it displays on the user's screen /// </summary> static public void ImportCommaDelimitedStream(FileStream fileStream, DataWorkspace dataWorkspace) { List<string[]> parsedData = new List<string[]>(); using (StreamReader streamReader = new StreamReader(fileStream)) { string line; string[] row; while ((line = streamReader.ReadLine()) != null) { row = line.Split(','); parsedData.Add(row); } } AddData(parsedData, dataWorkspace); } /// <summary> /// Take in a list of string arrays which contains /// all the parsed data for our Contacts entity /// </summary> static private void AddData(List<string[]> dataList, DataWorkspace dataWorkspace) { foreach (string[] row in dataList) { Contact contact = dataWorkspace.ApplicationData.Contacts.AddNew(); contact.FirstName = row[0]; contact.MiddleName = row[1]; contact.LastName = row[2]; contact.AddressLine1 = row[3]; contact.AddressLine2 = row[4]; contact.City = row[5]; contact.State = row[6]; contact.ZipCode = row[7]; contact.Country = row[8]; contact.PhoneNumber1 = row[9]; contact.PhoneNumber2 = row[10]; contact.BirthDate = DateTime.Parse(row[11]); contact.MiscellaneousInformation = row[12]; } } } }
using System.Collections.Generic;
public class ImportDataFile
/// Import data from a comma delimited stream
/// And using the workspace that was passed in, insert the imported data
/// so it displays on the user's screen
static public void ImportCommaDelimitedStream(FileStream fileStream, DataWorkspace dataWorkspace)
List<string[]> parsedData = new List<string[]>();
using (StreamReader streamReader = new StreamReader(fileStream))
string line;
string[] row;
while ((line = streamReader.ReadLine()) != null)
row = line.Split(',');
parsedData.Add(row);
AddData(parsedData, dataWorkspace);
/// Take in a list of string arrays which contains
/// all the parsed data for our Contacts entity
static private void AddData(List<string[]> dataList, DataWorkspace dataWorkspace)
foreach (string[] row in dataList)
Contact contact = dataWorkspace.ApplicationData.Contacts.AddNew();
contact.FirstName = row[0];
contact.MiddleName = row[1];
contact.LastName = row[2];
contact.AddressLine1 = row[3];
contact.AddressLine2 = row[4];
contact.City = row[5];
contact.State = row[6];
contact.ZipCode = row[7];
contact.Country = row[8];
contact.PhoneNumber1 = row[9];
contact.PhoneNumber2 = row[10];
contact.BirthDate = DateTime.Parse(row[11]);
contact.MiscellaneousInformation = row[12];
Let’s do the final bit of coding now by adding the code to our “Import Data” button.
/// <summary> /// Import Data button method /// </summary> partial void ImportData_Execute() { // To invoke our own dialog, we have to do this inside of the "Main" Dispatcher // And, since this is a web application, we can't directly invoke the Silverlight OpenFileDialog // class, we have to first invoke our own Silverlight custom control (i.e. SelectFileWindow) // and that control will be able to invoke the OpenFileDialog class (via the Browse button) Dispatchers.Main.BeginInvoke(() => { SelectFileWindow selectFileWindow = new SelectFileWindow(); selectFileWindow.Closed += new EventHandler(selectFileWindow_Closed); selectFileWindow.Show(); }); } /// <summary> /// Invoked when our custom Silverlight window closes /// </summary> void selectFileWindow_Closed(object sender, EventArgs e) { SelectFileWindow selectFileWindow = (SelectFileWindow)sender; // Continue if they hit the OK button AND they selected a file if (selectFileWindow.DialogResult == true && (selectFileWindow.DocumentStream != null)) { ImportDataFile.ImportCommaDelimitedStream(selectFileWindow.DocumentStream, this.DataWorkspace); selectFileWindow.DocumentStream.Close(); } }
/// Import Data button method
partial void ImportData_Execute()
// To invoke our own dialog, we have to do this inside of the "Main" Dispatcher
// And, since this is a web application, we can't directly invoke the Silverlight OpenFileDialog
// class, we have to first invoke our own Silverlight custom control (i.e. SelectFileWindow)
// and that control will be able to invoke the OpenFileDialog class (via the Browse button)
Dispatchers.Main.BeginInvoke(() =>
SelectFileWindow selectFileWindow = new SelectFileWindow();
selectFileWindow.Closed += new EventHandler(selectFileWindow_Closed);
selectFileWindow.Show();
});
/// Invoked when our custom Silverlight window closes
void selectFileWindow_Closed(object sender, EventArgs e)
SelectFileWindow selectFileWindow = (SelectFileWindow)sender;
// Continue if they hit the OK button AND they selected a file
if (selectFileWindow.DialogResult == true && (selectFileWindow.DocumentStream != null))
ImportDataFile.ImportCommaDelimitedStream(selectFileWindow.DocumentStream, this.DataWorkspace);
selectFileWindow.DocumentStream.Close();
Here’s what’s going on at a high level when the Import Data button is clicked:
Optional Reading – Here is a lower level explanation of what is going on in this button method:
Technical Stuff: Since the Import Data button code does NOT by default run inside the Main UI thread, we have to invoke the “Main” thread (or Dispatcher) before calling our Silverlight dialog. That is the reason for wrapping this code inside the Dispatchers.Main.BeginInvoke() method. So why can’t we just open the OpenFileDialog directly? Why do we have to open a Silverlight dialog which then launches an OpenFileDialog? Well, this is really what makes the web application scenario different from the Desktop application. If you try to directly launch the OpenFileDialog you will get a “Dialogs must be user-initiated” error message. This is because Silverlight dialogs (like OpenFileDialog) can only be opened from “user actions”, like a button clicked event handler. The reason why this won’t work with LightSwitch is because we invoke the button logic asynchronously, so the Import Data button code is not considered to be “user-initiated”.
Technical Stuff: Since the Import Data button code does NOT by default run inside the Main UI thread, we have to invoke the “Main” thread (or Dispatcher) before calling our Silverlight dialog. That is the reason for wrapping this code inside the Dispatchers.Main.BeginInvoke() method.
So why can’t we just open the OpenFileDialog directly? Why do we have to open a Silverlight dialog which then launches an OpenFileDialog?
Well, this is really what makes the web application scenario different from the Desktop application. If you try to directly launch the OpenFileDialog you will get a “Dialogs must be user-initiated” error message. This is because Silverlight dialogs (like OpenFileDialog) can only be opened from “user actions”, like a button clicked event handler. The reason why this won’t work with LightSwitch is because we invoke the button logic asynchronously, so the Import Data button code is not considered to be “user-initiated”.
The C# and VB.NET code is available on MSDN Code Gallery here: http://code.msdn.microsoft.com/Visual-Studio-LightSwitch-1f8aa17e
Let me know if you have questions. Enjoy!
Matt Sampson -
Thanks for the sample Matt, just one suggestion - the title of your article is very misleading, my suggestion:
How do I: Import data (from CSV file) while in browser
@Gregid
Thanks Greg.
I always thought it could use some improvement.
I took your suggestion and slightly modified it.
how do i:import dbf file
Hi Matt,
Thanks for an awesome post. I was wondering how can one extend this import routine to incorporate data import from csv file to related (multiple) tables in at a time.
Cheers.
KD
This is great article. I try to use OpenFileDialog or SaveFileDialog in Web mode and always hava a problem with "Dialogs must be user-initiated".
Select File Dialog is solution to that problem.
Thanks a lot
Spaso Lazarevic
Thanks Matt: very useful article!
Could you post a complementary example of how to retrieve the file stored in the database, save it to a local folder, and eventually invoking an external application (i.a.e.: mspaint.exe) to open it?
Hi everybody,
nice sample Matt!
I have a question for a special Screen. I have a Customer Listdetail Screnn with all Customers and their Orders (orders are included in to the screen). What I need now is to add a new Customer through this screen and for example 20 new orders for him through the same screen. Is something like that possible?
@John_C - Hey John_C I'm not sure about that one. But I think you might want to try making your own template - msdn.microsoft.com/.../hh304432.aspx . And then that could be your "New screen"
Thanks Matt, through your hint I am founding the solution for my problem. It was the AddNew and ShowCreatNew method which I was looking for.
I want to use your class file method "AddData" to import data into a ListDetail Screen. Problem is that I couldn't access this screen. Where you define for a editable Screen the following code. "Contact contact = dataWorkspace.ApplicationData.Contacts.AddNew();. " And that what I need is something like that: Contact contact = dataWorkspace.ApplicationData.ContactListDetail.AddNew();. Do you have any idea how I can solve this problem?
thx.
@Andrew- I may not be answering your question here, but I think what you might want to do is just "AddNew" like I have in my code and then invoke the "Refresh()" functionality on the screen to make sure it refreshes (see this blog post on invoking refresh - blogs.msdn.com/.../odata-apps-in-lightswitch-part-2.aspx).
You can access the screen from this code just by doing "ScreenName.whatever" for example.
thanks for the quick answere. But that is not what I want :). What I have is a screen like that:
www.silverlightshow.net/.../image_e33de2b2-814e-4307-a62a-840bdd62b96a.png
(here is a Movie table and a showtime table in a Master detail Screen)
And what I want is to add at the same time Data in both tables. For example for each Movie I want to add 10 showtimes. I hope my problem is now a littel bit clear :).
@Andrew -
Thanks for the screenshot.
So there's maybe a couple different ways to do this.
I made a List and Details screen around Northwind Customer and the "Details" portion is the Orders entity
So each customer can have 1 or more Orders.
So I made a button on the Screen called "Method" and put the below code into it:
partial void Method_Execute()
Customer myCust = this.Customers.AddNew();
myCust.CustomerID = "ROCK2";
myCust.CompanyName = "ROCK COMPANY";
Order myOrder1 = this.Orders.AddNew();
myOrder1.OrderDate = DateTime.Now;
Order myOrder2 = this.Orders.AddNew();
myOrder2.OrderDate = DateTime.Now;
myCust.Orders.Add(myOrder1);
this.Save();
When the button is clicked it creates 1 Customer, 2 Orders, and relates the 2 orders to the created Customer. Then it saves it.
This may not be exactly what you are wanting but hopefully it gets you on the right track.
Hey thanks Matt for your quick answer. Your example works fine in the Customer class but when I use it in your "ImportDataFile.cs" again the method failed. From this "ImportDataFile.cs" I am only able to add from my csv file the Customer table with data, the Orders table don't get any notice.
@Andrew - if you share your code out with me I can see what's going on. I think that's the best option at the moment here if you are comfortable with that. Email it here - Raymond.M.Sampson@microsoft.com
Thx,
Matt