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

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();
}

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; }
}
}

Asp.Net Core with redis implementation gives 502 Error for high traffic

In Our application, we are having heavy traffic of users and it is around 2000 request per second.
We have created application in Asp.Net core and used dapper. We are using redis cache manager for distibuted caching purpose.
When we hosted this site and checked it for few (20 or 30) request per second, it was working fine. But when we are hitting more than around 50 requests per second, site is giving
502 - Web server received an invalid response while acting as a gateway or proxy server.
We changed redis caching to memory cache and then it started to work fine for all 2000 request s per second.
We are using redis version 3.2.100
So, here using redis we are not able to run this site for more requests and getting 502 error with heavy traffic.
Code written for Redis Cache
using Common;
using Common.DependencyInjection;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Text;
namespace Service.Caching
{
[TransientDependency(ServiceType = typeof(ICacheManager))]
public class RedisCacheManager : ICacheManager
{
private readonly string _redisHost;
private readonly int _redisPort;
private readonly string _redisPassword;
private readonly ConfigurationOptions _configurationOptions;
public RedisCacheManager()
{
_redisHost = ConfigItems.RedisHost;
_redisPassword = ConfigItems.RedisPassword;
_redisPort = ConfigItems.RedisPort;
_configurationOptions = new ConfigurationOptions();
configurationOptions.EndPoints.Add(_redisHost, redisPort);
_configurationOptions.Ssl = false;
//_configurationOptions.Password = _redisPassword;
_configurationOptions.AbortOnConnectFail = false;
_configurationOptions.SyncTimeout = int.MaxValue;
_configurationOptions.DefaultVersion = new Version(2, 8, 8);
_configurationOptions.WriteBuffer = 10000000;
_configurationOptions.KeepAlive = 180;
}
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="key">The key of the value to get.</param>
/// <returns>
/// The value associated with the specified key.
/// </returns>
public T Get<T>(string key)
{
using (var connection = ConnectionMultiplexer.Connect(_configurationOptions))
{
var db = connection.GetDatabase(-1);
//return (T)(object)db.StringGet(key);
return (T)ConvertToObject<T>(db.StringGet(key));
}
}
/// <summary>
/// Adds the specified key and object to the cache.
/// </summary>
/// <param name="key">key</param>
/// <param name="data">Data</param>
/// <param name="cacheTime">Cache time</param>
public void Set(string key, object data, int cacheTime)
{
if (data == null)
return;
DateTime expireDate;
if (cacheTime == 99)
expireDate = DateTime.Now + TimeSpan.FromSeconds(30);
else
expireDate = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
var value = (RedisValue)ConvertToByteArray(data);
using (var connection = ConnectionMultiplexer.Connect(_configurationOptions))
{
var db = connection.GetDatabase(-1);
db.StringSet(key, value, new TimeSpan(expireDate.Ticks));
}
}
/// <summary>
/// Gets a value indicating whether the value associated with the specified key is cached
/// </summary>
/// <param name="key">key</param>
/// <returns>
/// Result
/// </returns>
public bool IsSet(string key)
{
using (var connection = ConnectionMultiplexer.Connect(_configurationOptions))
{
var db = connection.GetDatabase(-1);
return db.KeyExists(key);
}
}
/// <summary>
/// Removes the value with the specified key from the cache
/// </summary>
/// <param name="key">/key</param>
public void Remove(string key)
{
using (var connection = ConnectionMultiplexer.Connect(_configurationOptions))
{
var db = connection.GetDatabase(-1);
db.KeyDelete(key);
}
}
/// <summary>
/// Removes items by pattern
/// </summary>
/// <param name="pattern">pattern</param>
public void RemoveByPattern(string pattern)
{
using (var connection = ConnectionMultiplexer.Connect(_configurationOptions))
{
var server = connection.GetServer(_redisHost, _redisPort);
var keysToRemove = server.Keys(pattern: "*" + pattern + "*");//-1, pattern);
foreach (var key in keysToRemove)
Remove(key);
}
}
/// <summary>
/// Clear all cache data
/// </summary>
public void Clear()
{
using (var connection = ConnectionMultiplexer.Connect(_configurationOptions))
{
var server = connection.GetServer(_redisHost, _redisPort);
//var keysToRemove = server.Keys(-1);
var keysToRemove = server.Keys();
foreach (var key in keysToRemove)
Remove(key);
}
}
/// <summary>
/// Converts to byte array.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>System.Byte[].</returns>
private byte[] ConvertToByteArray(object data)
{
if (data == null)
return null;
string serialize = JsonConvert.SerializeObject(data);
return Encoding.UTF8.GetBytes(serialize);
}
/// <summary>
/// Converts to object.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>System.Object.</returns>
private T ConvertToObject<T>(byte[] data)
{
try
{
return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(data));
}
catch (Exception ex)
{
return (T)Activator.CreateInstance(typeof(T));
}
}
}
}
Your calls to Getting / Setting / Removing keys / values from the Redis cache might be taking longer as you are creating a new ConnectionMultiplexer on each operation to Redis.
https://gist.github.com/JonCole/925630df72be1351b21440625ff2671f#file-redis-bestpractices-stackexchange-redis-md has some best practices while using StackExchange.Redis.
https://learn.microsoft.com/en-us/azure/redis-cache/cache-dotnet-how-to-use-azure-redis-cache#connect-to-the-cache shows the recommended usage pattern.
Also, look at the StackExchange.Redis documentation (https://stackexchange.github.io/StackExchange.Redis/Basics) which says "ecause the ConnectionMultiplexer does a lot, it is designed to be shared and reused between callers. You should not create a ConnectionMultiplexer per operation. It is fully thread-safe and ready for this usage. In all the subsequent examples it will be assumed that you have a ConnectionMultiplexer instance stored away for re-use".

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.

Error: Object references an unsaved transient instance

now I'm building a web application on asp-net using castle active record. When I was trying to save an entity with a has-many relation I got the error: "object references an unsaved transient instance - save the transient instance before flushing. Type: SupBoardModel.Entities.Output, Entity: SupBoardModel.Entities.Output#0". Searching on the web I found the causes of this error and some of its solutions but no one worked for me. The relation already have a property set to Cascade = ManyRelationCascadeEnum.All, one of a common suggestion around the web so... What is wrong here??? There is a piece of code for more information and understanding:
//In some part of my application
State state = new State();
//Set some fields
//...
state.Outputs = (List<Output>)Session["outputs"]; //Collection filled on a web form but not saved yet
//Here is the error
state.SaveAndFlush(); // Booom!!!!
//Part of a definition of Output(child entity)
[Serializable, ActiveRecord(Table = "SUPB_OUTPUTS")]
public class Output : ActiveRecordBase<Output>
{
private int _id;
/// <summary>
/// Primary key
/// </summary>
[PrimaryKey(PrimaryKeyType.SeqHiLo, "OUTPUT_ID", SequenceName = "SEQ_OUTPUT_ID")]
public int Id
{
get { return _id; }
set { _id = value; }
}
private string _label;
/// <summary>
/// Output custom label
/// </summary>
[Property("OUTPUT_LABEL")]
public string Label
{
get { return _label; }
set { _label = value; }
}
private State _state;
/// <summary>
/// StateRef owner (An output is only available for one state)
/// </summary>
[BelongsTo("OUTPUT_ID_STATE", Lazy = FetchWhen.OnInvoke)]
public State StateRef
{
get { return _state; }
set { _state = value; }
}
}
// Part of a definition of State(parent entity)
[Serializable, ActiveRecord(Table = "SUPB_STATES")]
public class State : ActiveRecordBase<State>
{
private int _id;
/// <summary>
/// Primary key
/// </summary>
[PrimaryKey(PrimaryKeyType.SeqHiLo, "STATE_ID", SequenceName = "SEQ_STATE_ID")]
public int Id
{
get { return _id; }
set { _id = value; }
}
private string _name;
/// <summary>
/// StateRef name
/// </summary>
[Property("STATE_NAME")]
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _description;
/// <summary>
/// StateRef description
/// </summary>
[Property("STATE_DESC")]
public string Description
{
get { return _description; }
set { _description= value; }
}
private IList<Output> _outputs;
/// <summary>
/// State outputs (Columns to display data)
/// </summary>
[HasMany(typeof(Output), Table = "SUPB_OUTPUTS", ColumnKey = "OUTPUT_ID_STATE", Lazy = true, Cascade = ManyRelationCascadeEnum.All)]
public IList<Output> Outputs
{
get { return _outputs; }
set { _outputs = value; }
}
}
This error is make me crazy. I hope that is a way to save the State without save each Output before. The cascade attribute has no change for me, all options (All, AllDeleteOrfan, SaveUpdate) give me the same result. This case is very common and is mentioned on http://docs.castleproject.org/%28X%281%29S%28znghcs55lveeljjvqg21vni4%29%29/Active%20Record.Getting%20Started.ashx but is a mystery for me. Can any body help me??
Thanks
Menrique
Ok, I put Cascade=CascadeEnum.All in the StateRef field of the Output, something like that:
/// <summary>
/// StateRef owner (An output is only available for one state)
/// </summary>
[BelongsTo("OUTPUT_ID_STATE", Lazy = FetchWhen.OnInvoke, Cascade=CascadeEnum.All)]
public State StateRef
{
get { return _state; }
set { _state = value; }
}
And It WORK!!! So is not enough to put Cascade = ManyRelationCascadeEnum.All only in the relation hasMany of the parent entity, is necesary in the child entity too.
Thanks
Menrique

MSBuild Concat task only deals with ASCII files

I am using the MSBuild copy and then Concat to concatenate multiple SQL files. Some of the SQL files are in some other format (opens in notepad), but after Concat gets to the files, there are strange characters all over the resulting files.
Can anyone help me, has anyone experienced this before? How can I convert all SQL files to ascii, or make the Concat task work properly?
Well I got around it by writing a utility C# class. It is not designed to handle exceptions, they are handled by the calling class or unhandled in the console.
It converts the files to ASCII, removes any invalid characters and rewrites the files.
I spent a while wondering why I was getting access-denied issues, make sure you have all the files you want to convert checked out before you convert them!
Here it is if you are interested:
using System;
using System.IO;
using System.Text;
/// <summary>
/// Manages encoding of text files.
/// </summary>
public class TextFileEncoding
{
/// <summary>
/// Main command-line entry.
/// </summary>
/// <param name="args"></param>
public static void Main(string[] args)
{
if (args.Length > 0)
{
string argParam = args[0].ToString().Trim().ToLower();
switch (argParam)
{
// Return encoding for a file.
case "get":
Encoding enc = GetEncoding(args[1].ToString());
Console.WriteLine(enc.EncodingName);
break;
// convert encoding for a path with a given pattern to ASCII.
case "toascii":
int count = ConvertToAscii(args[1].ToString(), args.Length == 3 ? args[2] : "*.*");
Console.WriteLine("Successfully converted " + count + " files.");
break;
default:
Console.WriteLine("Invalid parameter. Expected get or toascii");
break;
}
}
else
{
Console.WriteLine("Missing filename parameter.\nFormat: TextFileEncoding.exe [get|toascii] <TextFile> <Path> <Pattern>");
}
}
/// <summary>
/// Returns the encoding of the filename at the specified path.
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public static Encoding GetEncoding(string filename)
{
StreamReader fileStream = new StreamReader(filename, true);
return GetEncoding(fileStream);
}
/// <summary>
/// Returns the encoding of the file stream.
/// </summary>
/// <param name="fileStream"></param>
/// <returns></returns>
public static Encoding GetEncoding(StreamReader fileStream)
{
return fileStream.CurrentEncoding;
}
/// <summary>
/// Converts all files to ascii encoding.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static int ConvertToAscii(string path, string pattern)
{
DirectoryInfo pathInfo = new DirectoryInfo(path);
int count = 0;
// Get files in directory, recursively.
FileInfo[] files = pathInfo.GetFiles(pattern, SearchOption.AllDirectories);
foreach (FileInfo file in files)
{
// Encode to ASCII.
if (EncodeAscii(file))
{
count++;
}
}
return count;
}
/// <summary>
/// Converts file to ascii.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
private static bool EncodeAscii(FileInfo file)
{
ASCIIEncoding encoder = new ASCIIEncoding();
// Get content of file as string.
string content = File.ReadAllText(file.FullName);
// Convert to an ASCII byte array.
byte[] asciiString = encoder.GetBytes(content);
// Convert to string, so all unknown characters get removed.
string cleanString = encoder.GetString(asciiString);
// Convert back to byte array for writing to file.
byte[] cleanBytes = encoder.GetBytes(cleanString);
// Delete and rewrite file as ASCII.
file.Delete();
File.WriteAllBytes(file.FullName, cleanBytes);
return true;
}
}