I have been struggling to find a way of persisting an SQLite database on a Pi under Win IoT which can be accessed by different background applications (not concurrently).
I thought I had the answer when I discovered Libraries (Music, Pictures, Videos - but perversely not Documents, without more work). I can create a text file in one app and write it to the Pictures library's default folder. I can then read the text file with another app. File.Exists returns true. Bingo (I thought)!
However, SQLite will not create a database in the folder or open an existing database that I copy to the folder. SQLite.Net.SQLiteConnection returns an SQLite exception: "Could not open database file: C:\Data\Users\DefaultAccount\Pictures\MyDb.db (CannotOpen)" - no further clues.
The folder appears to grant full permissions. Does anyone have any ideas, please?
Creating and Writing a text file:
using System;
using Windows.ApplicationModel.Background;
using System.IO;
using System.Diagnostics;
//*** NOTE: Pictures Library checked in Package.appxmanifest 'Capabilities'
namespace LibraryTest
{
public sealed class StartupTask : IBackgroundTask
{
private BackgroundTaskDeferral Deferral;
public async void Run (IBackgroundTaskInstance taskInstance)
{
Deferral = taskInstance.GetDeferral ();
var myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync
(Windows.Storage.KnownLibraryId.Pictures);
string path = myPictures.SaveFolder.Path;
Debug.WriteLine ($"'Pictures' Folder: {path}");
string newFilePath = Path.Combine (path, "TestTextFile.txt");
Debug.WriteLine ($"New File Path: {newFilePath}");
try {
using ( var stream = File.OpenWrite (newFilePath) ) {
using ( var writer = new StreamWriter (stream) ) {
writer.Write ("This is some test text.");
}
}
Debug.WriteLine ($"File created OK");
}
catch (Exception ex) { Debug.WriteLine ($"Exception: {ex.Message}"); }
}
}
}
Produced:
'Pictures' Folder: C:\Data\Users\DefaultAccount\Pictures
New File Path: C:\Data\Users\DefaultAccount\Pictures\TestTextFile.txt
File created OK
Reading:
using System;
using Windows.ApplicationModel.Background;
using System.IO;
using System.Diagnostics;
//*** NOTE: Pictures Library checked in Package.appxmanifest 'Capabilities'
namespace ReadLibraryTest
{
public sealed class StartupTask : IBackgroundTask
{
private BackgroundTaskDeferral Deferral;
public async void Run (IBackgroundTaskInstance taskInstance)
{
Deferral = taskInstance.GetDeferral ();
var myPictures = await Windows.Storage.StorageLibrary.GetLibraryAsync
(Windows.Storage.KnownLibraryId.Pictures);
string path = myPictures.SaveFolder.Path;
Debug.WriteLine ($"'Pictures' Folder: {path}");
string newFilePath = Path.Combine (path, "TestTextFile.txt");
Debug.WriteLine ($"New File Path: {newFilePath}");
try {
using ( var stream = File.OpenRead (newFilePath) ) {
using ( var reader = new StreamReader (stream) ) {
string fileContents = reader.ReadLine ();
Debug.WriteLine ($"First line of file: '{fileContents}'");
}
}
Debug.WriteLine ($"File read OK");
}
catch ( Exception ex ) { Debug.WriteLine ($"Exception: {ex.Message}"); }
}
}
}
Produced:
'Pictures' Folder: C:\Data\Users\DefaultAccount\Pictures
New File Path: C:\Data\Users\DefaultAccount\Pictures\TestTextFile.txt
First line of file: 'This is some test text.'
File read OK
However, SQLite will not create a database in the folder or open an
existing database that I copy to the folder.
SQLite.Net.SQLiteConnection returns an SQLite exception: "Could not
open database file: C:\Data\Users\DefaultAccount\Pictures\MyDb.db
(CannotOpen)" - no further clues.
Yes, I reproduced this issue. It seems this folder does not work with SQLite file operations but I don't know where the problem is.
As a workaround, you can use PublisherCacheFolder. I create the .db file and write data in one background app. And read the data from another background app. It works.
Contact class:
public sealed class Contact
{
public int Id { get; set; }
public string Name { get; set; }
}
Create and write file:
StorageFolder sharedFonts = Windows.Storage.ApplicationData.Current.GetPublisherCacheFolder("test");
var sqlpath = System.IO.Path.Combine(sharedFonts.Path, "MyDb.db");
using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), sqlpath))
{
conn.CreateTable<Contact>();
for (var i = 0; i < 100; i++)
{
Contact contact = new Contact()
{
Id = i,
Name = "A"
};
conn.Insert(contact);
}
}
Read file:
StorageFolder sharedFonts = Windows.Storage.ApplicationData.Current.GetPublisherCacheFolder("test");
var sqlpath = System.IO.Path.Combine(sharedFonts.Path, "MyDb.db");
using (SQLite.Net.SQLiteConnection conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), sqlpath))
{
var query = conn.Table<Contact>().Where(v => v.Name.Equals("A"));
foreach (var stock in query)
Debug.WriteLine("contact: " + stock.Id);
}
To use this publisher folder you need add the following lines in Package.appxmanifest:
<Extensions>
<Extension Category="windows.publisherCacheFolders">
<PublisherCacheFolders>
<Folder Name="test" />
</PublisherCacheFolders>
</Extension>
</Extensions>
Thanks, Rita. Worked very well. For the benefit of anyone reading, I am using the async version of SqlLite and create the connection as follows:
const string FileName = "MyFile.db";
string DbDir;
string DbPath;
Constructor:
DbDir = Windows.Storage.ApplicationData.Current.GetPublisherCacheFolder("test").Path;
DbPath = Path.Combine (DbDir, DbFileName);
public SQLite.Net.Async.SQLiteAsyncConnection GetConnectionAsync ()
{
var connectionFactory = new Func<SQLite.Net.SQLiteConnectionWithLock>(()=>
new SQLite.Net.SQLiteConnectionWithLock(new SQLitePlatformWinRT(),
new SQLite.Net.SQLiteConnectionString(DbPath, storeDateTimeAsTicks: false)));
var asyncConnection = new SQLiteAsyncConnection(connectionFactory);
return asyncConnection;
}
Then, for instance, read a table of type Parms:
public async Task<Parms> ReadParmsAsync ()
{
var db = GetConnectionAsync ();
var query = db.Table<Parms> ().Where (p => p.Id == 1);
return await query.FirstOrDefaultAsync ();
}
My concern about the SQLite async connection is that it is not IDisposable. Therefore, will the 'factory' eventually run out of steam (memory, handles)? But I guess that is a subject for another thread.
Related
I have a test script in Selenium Webdriver using C# in which I read data from a .txt external file.
The path is fixed on the script, indicating a folder on my computer. But in the future other people will run this script in other computers and they will have to adjust the path manually directly on the script.
Is it possible to set the path C:\Users\...\myData.txt like a kind of a variable, I mean, not being permanent on the body of the script?
This is the part of the script:
using System;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System.IO;
using System.Collections;
using System.Text;
namespace SeleniumTests
{
[TestFixture]
public class Principal
{
IWebDriver driver = null;
[SetUp]
public void SetUp()
{
ChromeOptions options = new ChromeOptions();
options.AddArguments("--disable-infobars");
options.AddArguments("start-maximized");
driver = new ChromeDriver(options);
}
public class DataTXT
{
public string example1{ get; set; }
public string example2{ get; set; }
public string example3{ get; set; }
public string example4{ get; set; }
}
public static IEnumerable DataTXT
{
get
{
string linha;
using (FileStream readFile =
new FileStream(#"C:\Users\...\myData.txt", FileMode.Open, FileAccess.Read))
{
var reader = new StreamReader(readFile, Encoding.GetEncoding("iso-8859-1"));
while ((line = reader.ReadLine()) != null)
{
var column = line.Split(';');
yield return new DataTXT
{
example1 = column[0],
example2 = column[1],
example3 = column[2],
example4 = column[3]
};
}
reader.Close();
readFile.Close();
}
}
}
Did you try? It is just a string.
public string FilePath { get; set: }
using (FileStream readFile =
new FileStream(FilePath, FileMode.Open, FileAccess.Read))
{
You can pass data in a command line
static int Main(string[] args)
I may be misunderstanding your question a bit as I'm slightly new here. But if you are just wanting a path variable that isn't statically defined and can be changed by the users; you could use the config file to define it and point to the config instead. (Or ConfigurationManager MSDN-ConfigManager)
There are a number of ways you can do this.
You can create the path inside your project and know where it is relative to \bin.
I see you are using NUnit. NUnit has TestContext.CurrentContext.TestDirectory that will return the path of the test dll. If you add the txt file to your solution and copy it in with the dll with a post-build event, it will always be in the TestDirectory.
Question solved by making the relative path, same, adding my .txt from each project in Solution Explorer, in the respective csprojs.
Imagining that the solution is the root and placed my .txt in the same relative path used in the new FileStream (path).
In Solution Explorer, right-clicked the .txt and went to Properties. Under Copy To Output Directory > Copy if newer.
#Kaj & #papparazzo, I'd be glad if you took off the negative votes of this question, if you want.
Have installed NUGet packages for Xamarin.Forms for PCL. I have 4 projects for Droid, iOS, Win 8.1 and WinPhone 8.1. Tried to connect my database, but encountered trouble: my Win and WinPhone projects don`t see it or return me the wrong path. Followed official Xamarin.Forms forum.
Interface:
public interface ISQLite
{
string GetDatabasePath(string filename);
}
WInPhone Class:
using System.IO;
using Windows.Storage;
using Baumizer.WinPhone;
using Xamarin.Forms;
[assembly: Dependency(typeof(SQLite_WinPhone))]
namespace Baumizer.WinPhone
{
public class SQLite_WinPhone:ISQLite
{
public SQLite_WinPhone() { }
public string GetDatabasePath(string filename)
{
return Path.Combine(ApplicationData.Current.LocalFolder.Path, filename);
}
}}
This class for selecting info:
public class DataBase
{
SQLiteConnection connection;
public DataBase()
{
connection= new SQLiteConnection(DependencyService.Get<ISQLite>().GetDatabasePath("Database.db"));
}
public IEnumerable<Group> GetGroups()
{
return connection.Query<Group>("SELECT * FROM [Scedule] WHERE [facultyName] =" + "'" + Data.CurrentFaculty + "'");
}
}
It works well on Android. On WinPhone I get exception of SQLite - no such table: Scedule. I open the local directory for emulator where db was - 0Kb.
I put db to Assets and set BuildInAction to Content. What`s wrong? Need help
On forum find this code, putted to OnLaunched(...) in WinPhone App.cs:
if(await ApplicationData.Current.LocalFolder.GetItemAsync(Data.DataBaseName) == null)
{
StorageFile databaseFile = await Package.Current.InstalledLocation.GetFileAsync($"Assets\\{Data.DataBaseName}");
await databaseFile.CopyAsync(ApplicationData.Current.LocalFolder);
}
It copies DB if it's not exist, but it is. May be I need to delete DB 0Kb, but I don't know how to do this.
It's work, OnLaunched():
try
{
await ApplicationData.Current.LocalFolder.GetItemAsync("Scedule.db");
}
catch (System.IO.FileNotFoundException)
{
StorageFile databaseFile =
await Package.Current.InstalledLocation.GetFileAsync($"Assets\\{"Scedule.db"}");
await databaseFile.CopyAsync(ApplicationData.Current.LocalFolder);
}
DB was uncorrectly copied. May be there are any another ways.
I've got a Windows 10 UWP application written in C#. I'm using SQLite to store my data locally. The issue I'm experiencing is that the file is never saved and/or retrieved using this code. It should work, but I can't find out what's wrong.
dbExists always evaluates to false, so what am I missing here?
private SQLiteConnection localConn;
private string dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "myDatabase.db");
public async void DBInit()
{
bool dbExists = false;
try
{
var store = await ApplicationData.Current.LocalFolder.GetFileAsync(dbPath);
dbExists = true;
}
catch { dbExists = false; }
if (!dbExists)
{
using (localConn = new SQLiteConnection(new SQLitePlatformWinRT(), dbPath))
{
// Create table
localConn.CreateTable<MyTable>();
}
}
else // CURRENTLY NOT FIRING!!
{}
}
Please consider using below code to create and access database file:
StorageFile notesFile = await storageFolder.CreateFileAsync(dbPath, CreationCollisionOption.OpenIfExists);
This will create new file if it does not exists and retrieve it when it is already created.
Please check my blog article to see more about UWP Data Storage:
https://mobileprogrammerblog.wordpress.com/2016/05/23/universal-windows-10-apps-data-storage/
I think you're missing this important piece of code:
SQLiteConnection.CreateFile("mydatabase.sqlite");
Do that first, then create a connection instance referencing the (now) created file.
Also, I'd suggest that you name the db with the .sqlite extension, so that the rest of the team and incoming devs, when then look at the db file artifact, can immediately tell that this is an sqlite database.
EDIT:
The method is a static method. So you would use it like this...
using System.Data.SQLite;
namespace sqlite_sample
{
class Program
{
static void Main(string[] args)
{
SQLiteConnection.CreateFile("sample.db");
}
}
}
The following will not work:
var conn = SQLiteConnection(...);
conn.CreateFile(dbPath); //<-- static methods can't be invoked at the instance level...
I am tired of all these "upload to S3" examples and tutorials that don't work , can someone just show me an example that simply works and is super easy?
well here are the instruction that you have to follow to get a fully working demo program ...
1-Download and install the Amazon web services SDK for .NET which you can find in (http://aws.amazon.com/sdk-for-net/). because I have visual studio 2010 I choose to install the 3.5 .NET SDK.
2- open visual studio and make a new project , I have visual studio 2010 and I am using a console application project.
3- add reference to AWSSDK.dll , it is installed with the Amazon web service SDK mentioned above , in my system the dll is located in "C:\Program Files (x86)\AWS SDK for .NET\bin\Net35\AWSSDK.dll".
4- make a new class file ,call it "AmazonUploader" here the complete code of the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Amazon;
using Amazon.S3;
using Amazon.S3.Transfer;
namespace UploadToS3Demo
{
public class AmazonUploader
{
public bool sendMyFileToS3(string localFilePath, string bucketName, string subDirectoryInBucket, string fileNameInS3)
{
// input explained :
// localFilePath = the full local file path e.g. "c:\mydir\mysubdir\myfilename.zip"
// bucketName : the name of the bucket in S3 ,the bucket should be alreadt created
// subDirectoryInBucket : if this string is not empty the file will be uploaded to
// a subdirectory with this name
// fileNameInS3 = the file name in the S3
// create an instance of IAmazonS3 class ,in my case i choose RegionEndpoint.EUWest1
// you can change that to APNortheast1 , APSoutheast1 , APSoutheast2 , CNNorth1
// SAEast1 , USEast1 , USGovCloudWest1 , USWest1 , USWest2 . this choice will not
// store your file in a different cloud storage but (i think) it differ in performance
// depending on your location
IAmazonS3 client = Amazon.AWSClientFactory.CreateAmazonS3Client(RegionEndpoint.EUWest1);
// create a TransferUtility instance passing it the IAmazonS3 created in the first step
TransferUtility utility = new TransferUtility(client);
// making a TransferUtilityUploadRequest instance
TransferUtilityUploadRequest request = new TransferUtilityUploadRequest();
if (string.IsNullOrEmpty(subDirectoryInBucket))
{
request.BucketName = bucketName; //no subdirectory just bucket name
}
else
{ // subdirectory and bucket name
request.BucketName = bucketName + #"/" + subDirectoryInBucket;
}
request.Key = fileNameInS3 ; //file name up in S3
request.FilePath = localFilePath; //local file name
utility.Upload(request); //commensing the transfer
return true; //indicate that the file was sent
}
}
}
5- add a configuration file : right click on your project in the solution explorer and choose "add" -> "new item" then from the list choose the type "Application configuration file" and click the "add" button. a file called "App.config" is added to the solution.
6- edit the app.config file : double click the "app.config" file in the solution explorer the edit menu will appear . replace all the text with the following text :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="AWSProfileName" value="profile1"/>
<add key="AWSAccessKey" value="your Access Key goes here"/>
<add key="AWSSecretKey" value="your Secret Key goes here"/>
</appSettings>
</configuration>
you have to modify the above text to reflect your Amazon Access Key Id and Secret Access Key.
7- now in the program.cs file (remember this is a console application) write the following code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UploadToS3Demo
{
class Program
{
static void Main(string[] args)
{
// preparing our file and directory names
string fileToBackup = #"d:\mybackupFile.zip" ; // test file
string myBucketName = "mys3bucketname"; //your s3 bucket name goes here
string s3DirectoryName = "justdemodirectory";
string s3FileName = #"mybackupFile uploaded in 12-9-2014.zip";
AmazonUploader myUploader = new AmazonUploader();
myUploader.sendMyFileToS3(fileToBackup, myBucketName, s3DirectoryName, s3FileName);
}
}
}
8- replace the strings in the code above with your own data
9- add error correction
and your program is ready
The solution of #docesam is for an old version of AWSSDK. Here is an example with the latest documentation of AmazonS3:
First open Visual Studio (I'm using VS2015) and create a New Project -> ASP.NET Web Application -> MVC.
Browse in Manage Nuget Package , the package AWSSDK.S3 and install it.
Now create a class named AmazonS3Uploader, then copy and paste this code:
using System;
using Amazon.S3;
using Amazon.S3.Model;
namespace AmazonS3Demo
{
public class AmazonS3Uploader
{
private string bucketName = "your-amazon-s3-bucket";
private string keyName = "the-name-of-your-file";
private string filePath = "C:\\Users\\yourUserName\\Desktop\\myImageToUpload.jpg";
public async void UploadFile()
{
var client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
try
{
PutObjectRequest putRequest = new PutObjectRequest
{
BucketName = bucketName,
Key = keyName,
FilePath = filePath,
ContentType = "text/plain"
};
PutObjectResponse response = await client.PutObjectAsync(putRequest);
}
catch (AmazonS3Exception amazonS3Exception)
{
if (amazonS3Exception.ErrorCode != null &&
(amazonS3Exception.ErrorCode.Equals("InvalidAccessKeyId")
||
amazonS3Exception.ErrorCode.Equals("InvalidSecurity")))
{
throw new Exception("Check the provided AWS Credentials.");
}
else
{
throw new Exception("Error occurred: " + amazonS3Exception.Message);
}
}
}
}
}
Edit your Web.config file adding the next lines inside of <appSettings></appSettings> :
Now call your method UploadFile from HomeController.cs to test it:
public class HomeController : Controller
{
public ActionResult Index()
{
AmazonS3Uploader amazonS3 = new AmazonS3Uploader();
amazonS3.UploadFile();
return View();
}
....
Find your file in your Amazon S3 bucket and that's all.
Download my Demo Project
I have written a tutorial about this.
Uploading a file to S3 bucket using low-level API:
IAmazonS3 client = new AmazonS3Client("AKI...access-key...", "+8Bo...secrey-key...", RegionEndpoint.APSoutheast2);
FileInfo file = new FileInfo(#"c:\test.txt");
string destPath = "folder/sub-folder/test.txt"; // <-- low-level s3 path uses /
PutObjectRequest request = new PutObjectRequest()
{
InputStream = file.OpenRead(),
BucketName = "my-bucket-name",
Key = destPath // <-- in S3 key represents a path
};
PutObjectResponse response = client.PutObject(request);
Uploading a file to S3 bucket using high-level API:
IAmazonS3 client = new AmazonS3Client("AKI...access-key...", "+8Bo...secrey-key...", RegionEndpoint.APSoutheast2);
FileInfo localFile = new FileInfo(#"c:\test.txt");
string destPath = #"folder\sub-folder\test.txt"; // <-- high-level s3 path uses \
S3FileInfo s3File = new S3FileInfo(client, "my-bucket-name", destPath);
if (!s3File.Exists)
{
using (var s3Stream = s3File.Create()) // <-- create file in S3
{
localFile.OpenRead().CopyTo(s3Stream); // <-- copy the content to S3
}
}
#mejiamanuel57's solution works fine for small files under 15MB. For larger files, I was getting System.Net.Sockets.SocketException: The I/O operation has been aborted because of either a thread exit or an application request. Following improved solution works for larger files (tested with 50MB file):
...
public void UploadFile()
{
var client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);
var transferUtility = new TransferUtility(client);
try
{
TransferUtilityUploadRequest transferUtilityUploadRequest = new TransferUtilityUploadRequest
{
BucketName = bucketName,
Key = keyName,
FilePath = filePath,
ContentType = "text/plain"
};
transferUtility.Upload(transferUtilityUploadRequest); // use UploadAsync if possible
}
...
More info here.
The example on the AWS site worked for me:
https://docs.aws.amazon.com/AmazonS3/latest/dev/HLuploadFileDotNet.html
Although it was set to a different region which returned an error:
//private static readonly RegionEndpoint bucketRegion = RegionEndpoint.USWest2;
private static readonly RegionEndpoint bucketRegion = RegionEndpoint.USWest1;
I set up my bucket with Northern California, which is USWest1.
#mejiamanuel57 and #HoomanBahreini covers it very well but i would still like to show the official sample code from AWS SDK Code Examples:
https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/welcome.html
https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3
https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/dotnetv3/S3/S3_Basics/S3Bucket.cs#L45
/// <summary>
/// Shows how to upload a file from the local computer to an Amazon S3
/// bucket.
/// </summary>
/// <param name="client">An initialized Amazon S3 client object.</param>
/// <param name="bucketName">The Amazon S3 bucket to which the object
/// will be uploaded.</param>
/// <param name="objectName">The object to upload.</param>
/// <param name="filePath">The path, including file name, of the object
/// on the local computer to upload.</param>
/// <returns>A boolean value indicating the success or failure of the
/// upload procedure.</returns>
public static async Task<bool> UploadFileAsync(
IAmazonS3 client,
string bucketName,
string objectName,
string filePath)
{
var request = new PutObjectRequest
{
BucketName = bucketName,
Key = objectName,
FilePath = filePath,
};
var response = await client.PutObjectAsync(request);
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
Console.WriteLine($"Successfully uploaded {objectName} to {bucketName}.");
return true;
}
else
{
Console.WriteLine($"Could not upload {objectName} to {bucketName}.");
return false;
}
}
This is a modified service I wrote to upload json strings as json files both with sync and async methods:
public class AwsS3Service
{
private readonly IAmazonS3 _client;
private readonly string _bucketName;
private readonly string _keyPrefix;
/// <param name="bucketName">The Amazon S3 bucket to which the object
/// will be uploaded.</param>
public AwsS3Service(string accessKey, string secretKey, string bucketName, string keyPrefix)
{
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
_client = new AmazonS3Client(credentials);
_bucketName=bucketName;
_keyPrefix=keyPrefix;
}
public bool UploadJson(string objectName, string json, int migrationId, long orgId)
{
return Task.Run(() => UploadJsonAsync(objectName, json, migrationId, orgId)).GetAwaiter().GetResult();
}
public async Task<bool> UploadJsonAsync(string objectName, string json, int migrationId, long orgId)
{
var request = new PutObjectRequest
{
BucketName = $"{_bucketName}",
Key = $"{_keyPrefix}{migrationId}/{orgId}/{objectName}",
InputStream = new MemoryStream(Encoding.UTF8.GetBytes(json)),
};
var response = await _client.PutObjectAsync(request);
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
return true;
}
else
{
return false;
}
}
}
I have a piece of software that generates code for a C# project based on user actions. I would like to create a GUI to automatically compile the solution so I don't have to load up Visual Studio just to trigger a recompile.
I've been looking for a chance to play with Roslyn a bit and decided to try and use Roslyn instead of msbuild to do this. Unfortunately, I can't seem to find any good resources on using Roslyn in this fashion.
Can anyone point me in the right direction?
You can load the solution by using Roslyn.Services.Workspace.LoadSolution. Once you have done so, you need to go through each of the projects in dependency order, get the Compilation for the project and call Emit on it.
You can get the compilations in dependency order with code like below. (Yes, I know that having to cast to IHaveWorkspaceServices sucks. It'll be better in the next public release, I promise).
using Roslyn.Services;
using Roslyn.Services.Host;
using System;
using System.Collections.Generic;
using System.IO;
class Program
{
static void Main(string[] args)
{
var solution = Solution.Create(SolutionId.CreateNewId()).AddCSharpProject("Foo", "Foo").Solution;
var workspaceServices = (IHaveWorkspaceServices)solution;
var projectDependencyService = workspaceServices.WorkspaceServices.GetService<IProjectDependencyService>();
var assemblies = new List<Stream>();
foreach (var projectId in projectDependencyService.GetDependencyGraph(solution).GetTopologicallySortedProjects())
{
using (var stream = new MemoryStream())
{
solution.GetProject(projectId).GetCompilation().Emit(stream);
assemblies.Add(stream);
}
}
}
}
Note1: LoadSolution still does use msbuild under the covers to parse the .csproj files and determine the files/references/compiler options.
Note2: As Roslyn is not yet language complete, there will likely be projects that don't compile successfully when you attempt this.
I also wanted to compile a full solution on the fly. Building from Kevin Pilch-Bisson's answer and Josh E's comment, I wrote code to compile itself and write it to files.
Software Used
Visual Studio Community 2015 Update 1
Microsoft.CodeAnalysis v1.1.0.0 (Installed using Package Manager Console with command Install-Package Microsoft.CodeAnalysis).
Code
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.MSBuild;
namespace Roslyn.TryItOut
{
class Program
{
static void Main(string[] args)
{
string solutionUrl = "C:\\Dev\\Roslyn.TryItOut\\Roslyn.TryItOut.sln";
string outputDir = "C:\\Dev\\Roslyn.TryItOut\\output";
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
bool success = CompileSolution(solutionUrl, outputDir);
if (success)
{
Console.WriteLine("Compilation completed successfully.");
Console.WriteLine("Output directory:");
Console.WriteLine(outputDir);
}
else
{
Console.WriteLine("Compilation failed.");
}
Console.WriteLine("Press the any key to exit.");
Console.ReadKey();
}
private static bool CompileSolution(string solutionUrl, string outputDir)
{
bool success = true;
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
Solution solution = workspace.OpenSolutionAsync(solutionUrl).Result;
ProjectDependencyGraph projectGraph = solution.GetProjectDependencyGraph();
Dictionary<string, Stream> assemblies = new Dictionary<string, Stream>();
foreach (ProjectId projectId in projectGraph.GetTopologicallySortedProjects())
{
Compilation projectCompilation = solution.GetProject(projectId).GetCompilationAsync().Result;
if (null != projectCompilation && !string.IsNullOrEmpty(projectCompilation.AssemblyName))
{
using (var stream = new MemoryStream())
{
EmitResult result = projectCompilation.Emit(stream);
if (result.Success)
{
string fileName = string.Format("{0}.dll", projectCompilation.AssemblyName);
using (FileStream file = File.Create(outputDir + '\\' + fileName))
{
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(file);
}
}
else
{
success = false;
}
}
}
else
{
success = false;
}
}
return success;
}
}
}