MSBuild - how to create "critical section" - msbuild

I have a parallel build of multiple projects, each one of those at some point in time does invoke <Exec /> task. This exec task is running 3pty tool that crashes if there is another instance of this tool running. Is there some native way how to implement "mutex" in msbuild ?
The obvious solution (that works) is to make the build synchronous - but that is slowing down the whole build.

In the end I ended up writing my own msbuild extension library like below
msbuild part:
<UsingTask TaskName="MutexExec" AssemblyFile="$(CommonTasksAssembly)" />
C# part:
// <PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.9.0" />
// <PackageReference Include="Microsoft.Build.Tasks.Core" Version="16.9.0" />
// <PackageReference Include="Microsoft.Build.Framework" Version="16.9.0" />
using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks;
/// <summary>
/// Like a Exec task, but with critical section
/// </summary>
public class MutexExec : Exec
{
/// <summary>
/// Gets or sets mutex name
/// </summary>
[Required]
public string MutexName { get; set; }
/// <inheritdoc />
public override bool Execute()
{
var timeout = TimeSpan.FromMinutes(5);
var stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed < timeout)
{
bool createdNew;
using (var mutex = new Mutex(true, this.MutexName, out createdNew))
{
if (createdNew)
{
bool result = base.Execute();
try
{
this.Log.LogMessage(MessageImportance.Normal, "Releasing {0}", this.MutexName);
mutex.ReleaseMutex();
}
catch (Exception e)
{
this.Log.LogError("Failure releasing {0} - {1}", this.MutexName, e);
}
return result;
}
}
this.Log.LogMessage(MessageImportance.Normal, "Waiting for {0} - ellapsed {1}", this.MutexName, stopwatch.Elapsed);
Thread.Sleep(5000);
continue;
}
this.Log.LogError("Failed to acquire {0} in {1}.", this.MutexName, timeout);
return false;
}
}

Related

Hangfire Clarification on SlidingInvisibilityTimeout

the hangfire docs state:
One of the main disadvantage of raw SQL Server job storage implementation – it uses the polling technique to fetch new jobs. Starting from Hangfire 1.7.0 it’s possible to use TimeSpan.Zero as a polling interval, when SlidingInvisibilityTimeout option is set.
and i'm using these SqlServerStorageOptions as suggested:
var options = new SqlServerStorageOptions
{
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero
};
Nowhere does it say what the SlidingInvisibilityTimeout actually means, can anyone clarify?
My scenario is that i have approx 1000 emails to send every morning and i've been tripping the Office365 throttling limitations of 30 messages per minute and getting rejected, so i'm using Hangfire to queue them up in a single workerthread, and adding a 2 second Thread.Sleep at the end of each task. this is working great but i'm getting increased CPU usage of about 20% caused by hangfire (as reported here) and it's causing frequent timeouts when the server is busy.
the behaviour i'm trying to achieve is:
at the end of each job, check is there another one straight away and take the next task.
if there are no jobs in the queue, check back in 5 minutes and don't touch the SQL server until then.
Thanks for any assistance.
in the end i wrote a lightweight alternative approach using a thread in a while loop to monitor a folder for text files containing a serialized object with the email parameters. it has almost no overhead and complies with the office365 throttling policy. posting here in case it is of use to anyone else, built for asp.net and should be easy to adapt for other scenarios.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web.Hosting;
namespace Whatever
{
public class EmailerThread
{
public delegate void Worker();
private static Thread worker; // one worker thread for this application // https://stackoverflow.com/questions/1824933/right-way-to-create-thread-in-asp-net-web-application
public static string emailFolder;
public static int ScanIntervalMS = 2000; // office365 allows for 30 messages in a 60 second window, a 2 second delay plus the processing time required to connect & send each message should safely avoid the throttling restrictions.
/// <summary>
/// Must be invoked from Application_Start to ensure the thread is always running, if the applicationpool recycles etc
/// </summary>
public static void Init()
{
// create the folder used to store serialized files for each email to be sent
emailFolder = Path.Combine(HostingEnvironment.ApplicationPhysicalPath, "App_Data", "_EmailOutbox");
Directory.CreateDirectory(emailFolder);
worker = new Thread(new ThreadStart(new Worker(ScanForEmails)));
worker.Start();
}
/// <summary>
/// Serialize an object containing all the email parameters to a text file
/// Call this object
/// </summary>
/// <param name="e"></param>
public static void QueueEmail(EmailParametersContainer e)
{
string filename = Guid.NewGuid().ToString() + ".txt";
File.WriteAllText(Path.Combine(emailFolder, filename), JsonConvert.SerializeObject(e));
}
public static void ScanForEmails()
{
var client = new System.Net.Mail.SmtpClient(Settings.SmtpServer, 587);
client.EnableSsl = true;
client.UseDefaultCredentials = false;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Credentials = new System.Net.NetworkCredential(Settings.smtpUser, Settings.smtpPass);
client.Timeout = 5 * 60 * 1000; // 5 minutes
// infinite loop to keep scanning for files
while (true)
{
// take the oldest file in the folder and process it for sending
var nextFile = new DirectoryInfo(emailFolder).GetFiles("*.txt", SearchOption.TopDirectoryOnly).OrderBy(z => z.CreationTime).FirstOrDefault();
if (nextFile != null)
{
// deserialize the file
EmailParametersContainer e = JsonConvert.DeserializeObject<EmailParametersContainer>(File.ReadAllText(nextFile.FullName));
if (e != null)
{
try
{
MailMessage msg = new MailMessage();
AddEmailRecipients(msg, e.To, e.CC, e.BCC);
msg.From = new MailAddress(smtpUser);
msg.Subject = e.Subject;
msg.IsBodyHtml = e.HtmlFormat;
msg.Body = e.MessageText;
if (e.FilePaths != null && e.FilePaths.Count > 0)
foreach (string file in e.FilePaths)
if (!String.IsNullOrEmpty(file) && File.Exists(file))
msg.Attachments.Add(new Attachment(file));
client.Send(msg);
msg.Dispose();
// delete the text file now that the job has successfully completed
nextFile.Delete();
}
catch (Exception ex)
{
// Log the error however suits...
// rename the .txt file to a .fail file so that it stays in the folder but will not keep trying to send a problematic email (e.g. bad recipients or attachment size rejected)
nextFile.MoveTo(nextFile.FullName.Replace(".txt", ".fail"));
}
}
}
Thread.Sleep(ScanIntervalMS); // wait for the required time before looking for another job
}
}
/// <summary>
///
/// </summary>
/// <param name="msg"></param>
/// <param name="Recipients">Separated by ; or , or \n or space</param>
public static void AddEmailRecipients(MailMessage msg, string To, string CC, string BCC)
{
string[] list;
if (!String.IsNullOrEmpty(To))
{
list = To.Split(";, \n".ToCharArray());
foreach (string email in list)
if (email.Trim() != "" && ValidateEmail(email.Trim()))
msg.To.Add(new MailAddress(email.Trim()));
}
if (!String.IsNullOrEmpty(CC))
{
list = CC.Split(";, \n".ToCharArray());
foreach (string email in list)
if (email.Trim() != "" && ValidateEmail(email.Trim()))
msg.CC.Add(new MailAddress(email.Trim()));
}
if (!String.IsNullOrEmpty(BCC))
{
list = BCC.Split(";, \n".ToCharArray());
foreach (string email in list)
if (email.Trim() != "" && ValidateEmail(email.Trim()))
msg.Bcc.Add(new MailAddress(email.Trim()));
}
}
public static bool ValidateEmail(string email)
{
if (email.Contains(" ")) { return false; }
try
{
// rely on the .Net framework to validate the email address, rather than attempting some crazy regex
var m = new MailAddress(email);
return true;
}
catch
{
return false;
}
}
}
public class EmailParametersContainer
{
public string To { get; set; }
public string Cc { get; set; }
public string Bcc { get; set; }
public string Subject { get; set; }
public string MessageText { get; set; }
public List<string> FilePaths { get; set; }
public bool HtmlFormat { get; set; }
}
}

How to use ASP.Net MVC 4 to Bundle LESS files in Release mode?

I'm trying to have LESS files in my web project, and have the MVC 4 bundling functionality call into the dotLess library to turn the LESS into CSS, then minify the result and give it to the browser.
I found an example on the ASP.NET site (under the heading LESS, CoffeeScript, SCSS, Sass Bundling.). This has given me a LessTransform class that looks like this:
public class LessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
response.Content = dotless.Core.Less.Parse(response.Content);
response.ContentType = "text/css";
}
}
and this line in my BundleConfig class:
bundles.Add(new Bundle(
"~/Content/lessTest",
new LessTransform(),
new CssMinify()).Include("~/Content/less/test.less"));
finally I have the following line in my _Layout.cshtml, in the <head>:
#Styles.Render("~/Content/lessTest")
If I have the site in debug mode, this is rendered to the browser:
<link href="/Content/less/test.less" rel="stylesheet"/>
The rules in the .less file are applied, and following that link shows that the LESS has been correctly transformed into CSS.
However, if I put the site into release mode, this is rendered out:
<link href="/Content/less?v=lEs-HID6XUz3s2qkJ35Lvnwwq677wTaIiry6fuX8gz01" rel="stylesheet"/>
The rules in the .less file are not applied, because following the link gives a 404 error from IIS.
So it seems that something is going wrong with the bundling. How do I get this to work in release mode, or how do I find out what exactly is going wrong?
As a complement to the accepted answer, I created a LessBundle class, which is the Less eqivalent of the StyleBundle class.
LessBundle.cs code is:
using System.Web.Optimization;
namespace MyProject
{
public class LessBundle : Bundle
{
public LessBundle(string virtualPath) : base(virtualPath, new IBundleTransform[] {new LessTransform(), new CssMinify()})
{
}
public LessBundle(string virtualPath, string cdnPath)
: base(virtualPath, cdnPath, new IBundleTransform[] { new LessTransform(), new CssMinify() })
{
}
}
}
Usage is similar to the StyleBundle class, specifying a LESS file instead of a CSS file.
Add the following to your BundleConfig.RegisterBundles(BundleCollection) method:
bundles.Add(new LessBundle("~/Content/less").Include(
"~/Content/MyStyles.less"));
Update
This method works fine with optimization switched off, but I ran into some minor problems (with CSS resource paths) when optimization was switched on. After an hour researching the issue I discovered that I have reinvented the wheel...
If you do want the LessBundle functionality I describe above, check out System.Web.Optimization.Less.
The NuGet package can be found here.
Edited 12/8/2019 This is no longer an acceptable answer to this issue as there have been breaking changes in ASP.NET over the years. There are other answers further down that have modified this code or supplied other answers to help you fix this issue.
It appears that the dotless engine needs to know the path of the currently processed bundle file to resolve #import paths. If you run the process code that you have above, the result of the dotless.Core.Less.Parse() is an empty string when the .less file being parsed has other less files imported.
Ben Foster's response here will fix that by reading the imported files first:
Import Files and DotLess
Change your LessTransform file as follows:
public class LessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse bundle)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (bundle == null)
{
throw new ArgumentNullException("bundle");
}
context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();
var lessParser = new Parser();
ILessEngine lessEngine = CreateLessEngine(lessParser);
var content = new StringBuilder(bundle.Content.Length);
var bundleFiles = new List<FileInfo>();
foreach (var bundleFile in bundle.Files)
{
bundleFiles.Add(bundleFile);
SetCurrentFilePath(lessParser, bundleFile.FullName);
string source = File.ReadAllText(bundleFile.FullName);
content.Append(lessEngine.TransformToCss(source, bundleFile.FullName));
content.AppendLine();
bundleFiles.AddRange(GetFileDependencies(lessParser));
}
if (BundleTable.EnableOptimizations)
{
// include imports in bundle files to register cache dependencies
bundle.Files = bundleFiles.Distinct();
}
bundle.ContentType = "text/css";
bundle.Content = content.ToString();
}
/// <summary>
/// Creates an instance of LESS engine.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private ILessEngine CreateLessEngine(Parser lessParser)
{
var logger = new AspNetTraceLogger(LogLevel.Debug, new Http());
return new LessEngine(lessParser, logger, true, false);
}
/// <summary>
/// Gets the file dependencies (#imports) of the LESS file being parsed.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
/// <returns>An array of file references to the dependent file references.</returns>
private IEnumerable<FileInfo> GetFileDependencies(Parser lessParser)
{
IPathResolver pathResolver = GetPathResolver(lessParser);
foreach (var importPath in lessParser.Importer.Imports)
{
yield return new FileInfo(pathResolver.GetFullPath(importPath));
}
lessParser.Importer.Imports.Clear();
}
/// <summary>
/// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private IPathResolver GetPathResolver(Parser lessParser)
{
var importer = lessParser.Importer as Importer;
var fileReader = importer.FileReader as FileReader;
return fileReader.PathResolver;
}
/// <summary>
/// Informs the LESS parser about the path to the currently processed file.
/// This is done by using a custom <see cref="IPathResolver"/> implementation.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
/// <param name="currentFilePath">The path to the currently processed file.</param>
private void SetCurrentFilePath(Parser lessParser, string currentFilePath)
{
var importer = lessParser.Importer as Importer;
if (importer == null)
throw new InvalidOperationException("Unexpected dotless importer type.");
var fileReader = importer.FileReader as FileReader;
if (fileReader == null || !(fileReader.PathResolver is ImportedFilePathResolver))
{
fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
importer.FileReader = fileReader;
}
}
}
public class ImportedFilePathResolver : IPathResolver
{
private string currentFileDirectory;
private string currentFilePath;
public ImportedFilePathResolver(string currentFilePath)
{
if (string.IsNullOrEmpty(currentFilePath))
{
throw new ArgumentNullException("currentFilePath");
}
CurrentFilePath = currentFilePath;
}
/// <summary>
/// Gets or sets the path to the currently processed file.
/// </summary>
public string CurrentFilePath
{
get { return currentFilePath; }
set
{
currentFilePath = value;
currentFileDirectory = Path.GetDirectoryName(value);
}
}
/// <summary>
/// Returns the absolute path for the specified improted file path.
/// </summary>
/// <param name="filePath">The imported file path.</param>
public string GetFullPath(string filePath)
{
if (filePath.StartsWith("~"))
{
filePath = VirtualPathUtility.ToAbsolute(filePath);
}
if (filePath.StartsWith("/"))
{
filePath = HostingEnvironment.MapPath(filePath);
}
else if (!Path.IsPathRooted(filePath))
{
filePath = Path.GetFullPath(Path.Combine(currentFileDirectory, filePath));
}
return filePath;
}
}
The accepted answer does not work with recent changes to ASP.NET, so is no longer correct.
I've fixed the source in the accepted answer:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Hosting;
using System.Web.Optimization;
using dotless.Core;
using dotless.Core.Abstractions;
using dotless.Core.Importers;
using dotless.Core.Input;
using dotless.Core.Loggers;
using dotless.Core.Parser;
namespace Web.App_Start.Bundles
{
public class LessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse bundle)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (bundle == null)
{
throw new ArgumentNullException("bundle");
}
context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();
var lessParser = new Parser();
ILessEngine lessEngine = CreateLessEngine(lessParser);
var content = new StringBuilder(bundle.Content.Length);
var bundleFiles = new List<BundleFile>();
foreach (var bundleFile in bundle.Files)
{
bundleFiles.Add(bundleFile);
var name = context.HttpContext.Server.MapPath(bundleFile.VirtualFile.VirtualPath);
SetCurrentFilePath(lessParser, name);
using (var stream = bundleFile.VirtualFile.Open())
using (var reader = new StreamReader(stream))
{
string source = reader.ReadToEnd();
content.Append(lessEngine.TransformToCss(source, name));
content.AppendLine();
}
bundleFiles.AddRange(GetFileDependencies(lessParser));
}
if (BundleTable.EnableOptimizations)
{
// include imports in bundle files to register cache dependencies
bundle.Files = bundleFiles.Distinct();
}
bundle.ContentType = "text/css";
bundle.Content = content.ToString();
}
/// <summary>
/// Creates an instance of LESS engine.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private ILessEngine CreateLessEngine(Parser lessParser)
{
var logger = new AspNetTraceLogger(LogLevel.Debug, new Http());
return new LessEngine(lessParser, logger, true, false);
}
/// <summary>
/// Gets the file dependencies (#imports) of the LESS file being parsed.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
/// <returns>An array of file references to the dependent file references.</returns>
private IEnumerable<BundleFile> GetFileDependencies(Parser lessParser)
{
IPathResolver pathResolver = GetPathResolver(lessParser);
foreach (var importPath in lessParser.Importer.Imports)
{
yield return
new BundleFile(pathResolver.GetFullPath(importPath),
HostingEnvironment.VirtualPathProvider.GetFile(importPath));
}
lessParser.Importer.Imports.Clear();
}
/// <summary>
/// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private IPathResolver GetPathResolver(Parser lessParser)
{
var importer = lessParser.Importer as Importer;
var fileReader = importer.FileReader as FileReader;
return fileReader.PathResolver;
}
/// <summary>
/// Informs the LESS parser about the path to the currently processed file.
/// This is done by using a custom <see cref="IPathResolver"/> implementation.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
/// <param name="currentFilePath">The path to the currently processed file.</param>
private void SetCurrentFilePath(Parser lessParser, string currentFilePath)
{
var importer = lessParser.Importer as Importer;
if (importer == null)
throw new InvalidOperationException("Unexpected dotless importer type.");
var fileReader = importer.FileReader as FileReader;
if (fileReader == null || !(fileReader.PathResolver is ImportedFilePathResolver))
{
fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
importer.FileReader = fileReader;
}
}
}
}
Please note one known issue with this code as is is that LESS #imports must use their full paths, i.e. you must use #import "~/Areas/Admin/Css/global.less"; instead of #import "global.less";.
Looks like this works - I changed the Process method to iterate over the file collection:
public void Process(BundleContext context, BundleResponse response)
{
var builder = new StringBuilder();
foreach (var fileInfo in response.Files)
{
using (var reader = fileInfo.OpenText())
{
builder.Append(dotless.Core.Less.Parse(reader.ReadToEnd()));
}
}
response.Content = builder.ToString();
response.ContentType = "text/css";
}
This breaks if there are any #import statements in your less files though, in this case you have to do a bit more work, like this: https://gist.github.com/chrisortman/2002958
Already some great answers, here's a very simple solution I found for myself when trying to add MVC bundles that regard less files.
After creating your less file (for example, test.less), right click on it and under Web Compiler (get it here) option, select Compile File.
This generates the resulting css file from your less one, and also its minified version. (test.css and test.min.css).
On your bundle, just refer to the generated css file
style = new StyleBundle("~/bundles/myLess-styles")
.Include("~/Content/css/test.css", new CssRewriteUrlTransform());
bundles.Add(style);
And on your view, reference that bundle:
#Styles.Render("~/bundles/myLess-styles")
It should just work fine.

Nhibernate 2.1 with Spring on .net 4 throws System.ExecutionEngineException

I've got a website that works on .Net 3.5 running Nhibernate 2.1.0.4000. We are using spring as our ProxyFactory.
Everything works fine. I have tried to upgrade the project to .Net 4.0 using the wizard. It all went smoothly.
But as soon as the code tries to do anything with Nhibernate I get a very unfriendly exception of System.ExecutionEngineException. There is no stack trace and no inner exception.
We are using a NhibernateHelper class (below) and I've played around with it and the Session gets configured ok (no exception). The details havn't changed any from teh .net 3.5 version
The fist attempt to get something from the DB fails
The session is opened in the handler at the start of the request (not shown below).
We are also using unity which is setup on Appliation start (not sure if that has any bearing)
The First call to Nhibernate is
var emp = NHibernateHelper.CurrentSession.Get<SMS.DomainModel.Employee>(-200694);
I just want an error message that means something and gives me something to go on.
I've tried looking at NhibernateProfiler and all that is registered is the start of a session.
Any help is much appreciated
NhibernateHelper Class is as follows
using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using FluentNHibernate;
using FluentNHibernate.Cfg;
using HibernatingRhinos.NHibernate.Profiler.Appender;
using log4net;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Data.Configuration;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Event;
using NHibernate.Tool.hbm2ddl;
using SMS.Infrastructure.Persistence.Logging;
using SMS.Infrastructure.Persistence.Mappings;
using Configuration=NHibernate.Cfg.Configuration;
using System.Data.SqlClient;
namespace SMS.Infrastructure.Persistence
{
public static class NHibernateHelper
{
static readonly ILog Log = LogManager.GetLogger(typeof(NHibernateHelper));
static Configuration configuration;
public static ISessionFactory SessionFactory
{
get
{
return Singleton.sessionFactory;
}
}
// Lazy singleton pattern from http://www.yoda.arachsys.com/csharp/singleton.html
class Singleton
{
static Singleton() { }
internal static readonly ISessionFactory sessionFactory = CreateSessionFactory();
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
public static ISession CurrentSession
{
get { return SessionFactory.GetCurrentSession(); }
}
static ISessionFactory CreateSessionFactory()
{
try
{
Log.Info("Creating NHibernate session factory");
NHibernateProfiler.Initialize();
configuration = new Configuration();
try
{
// Try to configure NHibernate automagically...
configuration.Configure();
}
catch (HibernateConfigException e)
{
if (e.InnerException is IOException)
{
// Otherwise specify a file name manually.
configuration.Configure("hibernate.cfg.xml");
}
else
throw;
}
Log.Info("Registering custom SMS event listeners");
RegisterCustomListeners(configuration);
// Has someone specified a default_schema? No? try and guess
// it from the (Enterprise Library :/ ) query string.
if (!configuration.Properties.ContainsKey("default_schema"))
{
Log.Info("Setting default schema");
configuration.SetProperty("default_schema", GetDefaultSchema());
}
ISessionFactory sessionFactory = Fluently.Configure(configuration)
.Mappings(m =>
{
m.HbmMappings.AddFromAssemblyOf<BusinessUnitTypeMapping>();
m.FluentMappings.AddFromAssemblyOf<BusinessUnitTypeMapping>();
})
.BuildSessionFactory();
Log.Info("Session factory was configured successfully");
return sessionFactory;
}
catch (Exception ex)
{
throw new ArgumentNullException(ex.ToString());
}
}
/// <summary>
/// NHibernate allows custom event listeners to be registered in
/// config files or programmatically. Due to the re-use of configs in
/// SMS, we chose to do it via code.
///
/// This is how we extend NHibernate for SMS, and this is why
/// NHibernate is the best ORM..!
/// </summary>
static void RegisterCustomListeners(Configuration config)
{
if (config == null)
throw new ArgumentNullException("config");
// Event listeners for audit logging.
//config.SetListener(ListenerType.PreInsert, new AuditEventListener());
//config.SetListener(ListenerType.PreUpdate, new AuditEventListener());
//config.SetListener(ListenerType.PreDelete, new AuditEventListener());
// Event listener for wiring up .NET events between parent/child
// objects, and the intermediary mapping for Tasks.
//
// Warning: be careful with the order in which these listeners are
// added!!!
//
// BindEventsOnLoadListener must come before
// TaskAddresseeLoadEventListener for example otherwise OSM Task
// Decorators don't attach properly.
config.SetListeners(ListenerType.PostLoad, new IPostLoadEventListener[]
{
new BindEventsOnLoadListener(),
new TaskAddresseeLoadEventListener()
});
}
/// <summary>
/// Optional step: destroy and re-create the database scheme based on
/// the mapping files. Gives you a totally clean database in between
/// testing each fixture.
/// </summary>
public static void ExportDatabaseSchema(string fileName)
{
if (configuration == null)
CreateSessionFactory();
Log.InfoFormat("Exporting DDL to {0}", fileName);
var exporter = new SchemaExport(configuration);
exporter.SetOutputFile(fileName);
exporter.Execute(true, false, false);
}
public static void ExportFluentMappings(string directoryName)
{
Log.InfoFormat("Exporting fluent mappings to {0}", directoryName);
var model = new PersistenceModel();
model.AddMappingsFromAssembly(Assembly.GetAssembly(typeof(BusinessUnitTypeMapping)));
model.WriteMappingsTo(directoryName);
}
/// <summary>
/// hibernate's default_schema is worked out programmatically from the
/// Enterprise Library connection string. E.g.
/// Initial Catalog=OSM2Tests --> default_schema = SMS2Tests.dbo
/// </summary>
public static string GetDefaultSchema()
{
try
{
DatabaseSettings settings = DatabaseSettings.GetDatabaseSettings(new SystemConfigurationSource());
var connectionstring = ConfigurationManager.ConnectionStrings[settings.DefaultDatabase].ConnectionString;
var initialCatalog = new SqlConnectionStringBuilder(connectionstring).InitialCatalog;
return String.Format("{0}.dbo", initialCatalog);
}
catch (Exception)
{
throw new Exception("Could not get default schema from connection string.");
}
}
}
}
I was able to fix a similar problem by removing references to NHProf.

What's the fastest way to clear the isolatedstoragestore for a windows phone 7 application?

I'm currently working on an application which writes data to the IsolatedStorageStore. As part of the app, I'd like to implement a "clear all data/reset" button, but enumerating through all the files that exist and all the folders that exist is taking quite a bit of time. Is there a magic "reset" method or something I can use, or should I instead focus on optimizing the manual deletion process?
Or can I get away with not providing such functionality, and leave it to the user to uninstall/reinstall the application for a reset?
My hideous delete-all-files method is below:
/// <summary>
/// deletes all files in specified folder
/// </summary>
/// <param name="sPath"></param>
public static void ClearFolder(String sPath, IsolatedStorageFile appStorage)
{
//delete all files
string[] filenames = GetFilenames(sPath);
if (filenames != null)
{
foreach (string sFile in filenames)
{
DeleteFile(System.IO.Path.Combine(sPath, sFile));
}
}
//delete all subfolders if directory still exists
try
{
foreach (string sDirectory in appStorage.GetDirectoryNames(sPath))
{
ClearFolder(System.IO.Path.Combine(sPath, sDirectory) + #"\", appStorage);
}
}
catch (DirectoryNotFoundException ex)
{
//current clearing folder was deleted / no longer exists - return
return;
}
//try to delete this folder
try
{
appStorage.DeleteDirectory(sPath);
}
catch (ArgumentException ex) { }
}
/// <summary>
/// Attempts to delete a file from isolated storage - if the directory will be empty, it is also removed.
/// </summary>
/// <param name="sPath"></param>
/// <returns></returns>
public static void DeleteFile(string sPath)
{
using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
appStorage.DeleteFile(sPath);
String sDirectory = System.IO.Path.GetDirectoryName(sPath);
//if this was the last file inside this folder, remove the containing folder
if (appStorage.GetFileNames(sPath).Length == 0)
{
appStorage.DeleteDirectory(sDirectory);
}
}
}
/// <summary>
/// Returns an array of filenames in a given directory
/// </summary>
/// <param name="sHistoryFolder"></param>
/// <returns></returns>
public static string[] GetFilenames(string sDirectory)
{
using (IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
try
{
return appStorage.GetFileNames(sDirectory);
}
catch (DirectoryNotFoundException)
{
return null;
}
}
}
You're looking for the Remove() method.
Use it like this:
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.Remove();
}

How do I call static class methods from msbuild?

How does one call a class static method from msbuild and store its results in a list?
EDIT: Okay, let me explain a bit further. I am using sandcastle help file builder to generate documentation for my application. One of the requirements is that you must specify the documentation sources as follows:
<DocumentationSources>
<DocumentationSource sourceFile="$(MSBuildProjectDirectory)\..\src\myApp\bin\Debug\myApp.exe" xmlns="" />
<DocumentationSource sourceFile="$(MSBuildProjectDirectory)\..\src\myApp\bin\Debug\myApp.xml" xmlns="" />
</DocumentationSources>
Sandcastle Help File Builder comes with a utils assembly that has a way of retrieving all dll and xml files from a specified directory. I want to call the method from this assembly and store its result as a list of <DocumentationSource>. This is a static method which returns Collection<string>
Custom Tasks are great but potential overkill if you want to do something simple. I believe Draco is asking about the Property Functions feature in MSBuild 4.
An example of setting a property by using a static function (ripped directly from above page):
<Today>$([System.DateTime]::Now)</Today>
And to call a static function on parameters:
$([Class]:: Property.Method(Parameters))
Or, perhaps you want something crazier like inline tasks.
Create a custom task calling that static method and returning an array of ITaskItem.
Or
You could try using the MSBuild Extension Pack Assembly.Invoke :
<PropertyGroup>
<StaticMethodAssemblyPath>path</StaticMethodAssemblyPath>
</PropertyGroup>
<MSBuild.ExtensionPack.Framework.Assembly TaskAction="Invoke"
NetArguments="#(ArgsM)"
NetClass="StaticMethodClassName"
NetMethod="StaticMethodName"
NetAssembly="${StaticMethodAssemblyPath}">
<Output TaskParameter="Result" PropertyName="R"/>
</MSBuild.ExtensionPack.Framework.Assembly>
Generally, the most flexible option is to create a custom MSBuild task. This is all untested code meant to just to give you the idea:
In your msbuild file:
<UsingTask TaskName="FindFiles" AssemblyFile="FindFiles.dll" />
<!--
As you'll see below, SearchDirectory and SearchPatterns are input parameters,
MatchingFiles is an output parameter, SourceFiles is an ItemGroup assigned to
the output.
-->
<FindFiles SearchDirectory="$(MyDirectory)" SearchPatterns="*.dll;*.xml">
<Output ItemName="SourceFiles" TaskParameter="MatchingFiles" />
</FindFiles>
<!-- You can then use the generated ItemGroup output elsewhere. -->
<DocumentationSources>
<DocumentationSource sourceFile="#(SourceFiles)" xmlns="" />
</DocumentationSources>
FindFiles.cs:
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace FindFiles
{
public class FindFiles : Task
{
// input parameter
[Required]
public string SearchDirectory { get; set; }
// output parameter
[Required]
public string[] SearchPatterns { get; set; }
[Output]
public string[] MatchingFiles { get; private set; }
private bool ValidateParameters()
{
if (String.IsNullOrEmpty(SearchDirectory))
{
return false;
}
if (!Directory.Exists(SearchDirectory))
{
return false;
}
if (SearchPatterns == null || SearchPatterns.Length == 0)
{
return false;
}
return true;
}
// MSBuild tasks use the command pattern, this is where the magic happens,
// refactor as needed
public override bool Execute()
{
if (!ValidateParameters())
{
return false;
}
List<string> matchingFiles = new List<string>();
try
{
foreach (string searchPattern in SearchPatterns)
{
matchingFiles.AddRange(
Directory.GetFiles(SearchDirectory, searchPattern)
);
}
}
catch (IOException)
{
// it might be smarter to just let this exception fly, depending on
// how you want the task to behave
return false;
}
MatchingFiles = matchingFiles.ToArray();
return true;
}
}
}