I’m excited to announce the 0.4 release of Sassy Studio which brings the biggest missing functionality: automatic generation of CSS files. Right now it can only generate on save, but I’m hoping to add support for generating on build as well.

If you don’t need / want this functionality or have the CSS generated by another process like I do, you can disable it in the options.

Also in this release is better color matching, but there is still a lot to be desired here.

As always, you can grab it from within Visual Studio or on the gallery.

I just released a new version of Sassy Studio that addressed a couple of the biggest defects that it had:

  • Automatic color selection
  • Support for Comment / Uncomment selection
  • Warnings for deprecated features

I tried to get automatic generation of .css files on save, but ran into some problems with NSass that will have to be resolved before that is done, but the addition of the features above definitely makes Sassy Studio suck less™.

You can grab the update from the Visual Studio extension manager or you can download it from the Visual Studio Gallery

TL;DR: I got tired of MindScape's Web Workbench extension being slow and popping up modal dialogs telling me to buy the pro version, so I decided to add support for scss into Visual Studio myself.

You can install it from the Visual Studio Gallery and you can view the code on GitHub

Today I am announcing my first Visual Studio extension: SassyStudio. Right now, there is not a lot of support for sass/scss in Visual Studio (though I believe the web team is working on it), so I decided to do something about it.

Originally, I had been using MindScape’s Web Workbench for working with sass files which worked fine for the most part, but I disliked a few parts about it:

  • It was slow opening files that had many imports (in my case Zurb Foundation)
  • It pops up modal dialogs telling me that what I’m doing is a pro feature only. That messes up your flow greatly.

The only reason I was using it was so that I could generate a css file from the scss file, but that was never how I really wanted to work. I wanted to have the file generated automatically by ASP.NET MVC. Once I was able to do that, Web Workbench didn’t really give me a lot, so I started on SassyStudio.

Minimum Viable Weekend Project

Right now what you see is what I could get done in a couple of days. This is my first extension, so I had to do a lot of tinkering to figure this out and why there are very few features it supports. For v0.1 you get the following:

  • Syntax Highlighting
  • Region Outlining

Yup, that’s it. Until I know how to look up your current css color settings, you will need to go configure the colors yourself. It will look like plain text by default, but once you have them configured in settings you are good to go.

Don’t worry, I am planning to add support for the following:

  • Integrating with CSS Editor to provide CSS intellisense
  • Support for generating CSS file when saving
  • Intellisense support for sass (variable / mixin / function completion)
  • Multiline comments (this one is amusing)
  • Keyboard Shortcuts (the irony of this one is astounding, right?)
  • Support old sass style (indentation based)

Now, some of these are stupid easy to add, I just need to figure out the visual studio integration points. If you are interested in one of these getting sooner than another, let me know and I will see what I can do.

Acknowledgements

I should say that most of this wouldn’t have been possible (or would have taken much longer) without Web Essentials. I browsed their source code quite heavily to figure out how to do this stuff and there’s parts of their code in a lot of my files as I used their implementations as a starting point for mine.

Going Forward

I’m hoping to add more features to this extension in my spare time, but I don’t know when that will be since this was basically a weekend distraction while I transition from one project to the next. Since most of the features aren’t that tough, I would expect those to get done soon at a minimum, but the harder things might be a while.

You should still go download and install it though. If you find any problems, please make an issue on GitHub or berate me on twitter.

Sass makes CSS much easier to work with and since many of the popular front-end frameworks are developing their CSS using SASS, it becomes essential to have friction-free support for it with ASP.NET MVC.

I just spent a few hours trying to figure out how to get ASP.NET MVC and Sass to work well together. There are so many packages on NuGet that claim to support sass, you would think it would be easy, but it was more challenging than I had expected.

Ultimately, I was able to get NSass to work the way I wanted it to and went with that, but here is what I found out along the way.

The ideal configuration

Before setting out to find a runtime solution, I currently was using the System.Web.Optimization packages and build / save time compilation of the sass files.

While this worked out reasonably well for me, it wasn't working for our designer who just wanted to edit the sass files and refresh the browser to see the changes. This meant that we needed a solution that would catch file modifications and then regenerate the css which was being minified by the System.Web.Optimization packages.

SassAndCofee

The SassAndCofee.AspNet package is by Paul Betts and it uses the SassAndCoffee.Ruby package. It seems to be primarily convention based and does minification, but doesn't integrate with the System.Web.Optimization classes.

While I did get this to work, it didn't work out for me because this library doesn't seem to support some newer functionality (specifically the interpolation stuff) so it threw an exception while parsing and then went into an infinite loop while trying to handle that exception. It likely will work once the ruby parser (embedded in the assembluy) is updated.

Unfortunately, this flaw basically makes many other packages not work.

Project Site - https://github.com/xpaulbettsx/SassAndCoffee

BundleTransformer.SassAndScss

The BundleTransformer.SassAndScss packages is very similar to SassAndCoffee.Ruby. It uses IronRuby and uses a ruby file to parse and compile the sass, but it can integrate with System.Web.Optimization to support bundling and minification. This package had the same problem as SassAndCoffee in that it ran into a parsing exception when it hit the interpolation operator, but it didn't go into a loop.

Project Site - http://bundletransformer.codeplex.com/

Bundler

The Bundler package is a member of the ServiceStack family. With this you create a .bundle file in your content directory which has the sass files you want in that bundle, then you run bundler.cmd (or is run automatically if you have a visual studio extension installed) and it will generate the css files and optionally minify them.

It also comes with some extensions in for MVC that give you functionality similar to those in System.Web.Optimization. Ultimately, I decided against using this because it wasn't that different than what I was already doing, but it does work and could be the ideal solution for many other people.

Project Site - https://github.com/ServiceStack/Bundler

FubuMVC.Sass

I didn't evaluate the FubuMVC.Sass package since I am not using FubuMVC, but I will assume that it works.

NSass

There are a few packages to NSass:

  • NSass.Core is the core functionality to parse sass code and emit css
  • NSass.Optimization gives you a few classes that let you hook into System.Web.Optimization
  • NSass.Handler provides an IHttpHandler implementation that will parse requests to .scss files, which is what the System.Web.Optimization bundle will emit when in debug mode, so it's necessary when using it.

This project just wraps libsass and didn't run into the interpolation problem. This was simultaneously this simplest project to get running and the one that took the longest to get really running. It works absolutely fantastic out of the box, but you need to be sure that the machine you run it on has the Microsoft Visual C++ redistributable runtime installed. It took me forever to figure out that was the reason it wouldn't work on our web servers.

Once that problem is resolved, it is amazing.

Project Site - https://github.com/TBAPI-0KA/NSass

Notable Mentions

Well, before trying to get all of this working, I was using MindScape's Web Workbench, but even that had problems which is why I began looking for a runtime solution rather than a build / save time solution. Once I had found NSass, all I needed next was the Visual Studio support back which is why I ended up creating SassyStudio.

Sometimes things that work seamlessly by themselves have trouble working with each other. Autofac integrates seamlessly with ASP.NET MVC and Web API. NServiceBus has support for Autofac as one of it's IContainer implementations. What could go wrong?

A subtle problem arises when you start trying to do the "unit of work" approach to managing things like Entity Frameworks DbContext. We want a single DbContext instance for each web request and for it to be disposed at the end of each request so that we don't leak any resources. Autofac has great support for this through their ASP.NET integration so that certain components are scoped to have a single instance per request. This even works with ASP.NET Web API even.

NServiceBus... has a problem with this though. Since Autofac's integration starts a new lifetime scope with a specific key, when NServiceBus receives a message and tries to get a handler from Autofac, it is unable to fulfill the request because we are not in a web request.

The fix

So, how can we get everything to work together? Simple: throw out all the existing functionality and start from scratch. Sounds terrible but it isn't as hard as it sounds. Now, I should mention that I got the idea from this a few months ago while looking at the source code for the Orchard project, so credit should go to them for this brilliant implementation.

The infrastructure

There are three core interfaces we'll use to accomplish this: IWorkContext, IWorkContextScope, and IWorkContextAccessor. IWorkContext isn't necessary, but it is a somewhat nice abstraction between us and Autofac. IWorkContextScope is what tracks the ILifetimeScope for the request. IWorkContextAccessor is a component that can be injected into others that allow them to find the current work context or start a new scope. Below is the definition for IWorkContextAccessor and IWorkContextScope, and I'll put the code for the implementation at the end of this post.

public interface IWorkContextAccessor
{
    IWorkContext GetContext();
    IWorkContextScope CreateScope();
}

public interface IWorkContextScope : IDisposable
{
    IWorkContext Context { get; }
}

NServiceBus

Now that we have our infrastructure in place, we can hook into NServiceBus via the IMessageModule interface. We'll start a new scope in the HandleBeginMessage method and dispose of it in the HandleEndMessage method. Now, since there can be multiple threads handling messages, but there is only one IMessageModule instance per type, we'll need to store a separate instance of our current scope for each thread. Below is an implementation that does this.

public class WorkContextModule : IMessageModule, IDisposable
{
    readonly ThreadLocal<IWorkContextScope> CurrentScope = new ThreadLocal<IWorkContextScope>(false);
    public IWorkContextAccessor Accessor { get; set; }

    public void HandleBeginMessage()
    {
        CurrentScope.Value = Accessor.CreateScope();
    }

    public void HandleEndMessage()
    {
        var scope = CurrentScope.Value;
        if (scope != null)
        {
            try
            {
                scope.Dispose();
            }
            finally
            {
                CurrentScope.Value = null;
            }
        }
    }

    public void HandleError()
    {
    }

    public void Dispose()
    {
        CurrentScope.Dispose();
    }
}

Autofac

There's only a couple of issues that we need to tackle here. Unfortunately, ASP.NET MVC and Web API use different service locator providers, so we have to make our implementation work with both. On the plus side, we can leverage 50% of the work Autofac has already done since we can just override the functionality on the MVC side. The Web API side we aren't so lucky.

MVC

So for MVC we can just inherit from RequestLifetimeScopeProvider and provide the container to Autofac. From there, Autofac takes care of disposing the lifetime scope at the end of an HTTP request for us.

class WorkContextLifetimeScopeProvider : RequestLifetimeScopeProvider
{
    readonly IWorkContextAccessor Accessor;
    public WorkContextLifetimeScopeProvider(ILifetimeScope container)
        : base(container)
    {
        Accessor = container.Resolve<IWorkContextAccessor>();
    }

    protected override ILifetimeScope GetLifetimeScopeCore(Action<ContainerBuilder> configurationAction)
    {
        return Accessor.CreateScope().Resolve<ILifetimeScope>();
    }
}
Web API

There wasn't a simple way to extend the Autofac Web API integration, so I had to just copy the existing implemenation from the source. There's two components we need to implement here: IDependencyResolver and IDependencyScope. These are basically analogous to our IWorkContext and IWorkContextScope. I'll put implmentations at the end.

Tying it all together

The only thing left to do is wire up Autofac to make this all work. For the most part, you likely won't have many components that need to be scoped to the work context, but we want to make this as smooth as Autofac's ASP.NET integration.

public class InfrastructureModule : Module
{
    public const string CONTEXT_TAG = "WorkRequestScope";
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register(c => new DefaultWorkContext(c.Resolve<IComponentContext>()))
            .As<IWorkContext>()
            .InstancePerMatchingLifetimeScope(CONTEXT_TAG);

        builder.Register(c => new DefaultWorkContextAccessor(CONTEXT_TAG, c.Resolve<ILifetimeScope>()))
            .As<IWorkContextAccessor>()
            .SingleInstance();
    }
}

public static class InfrastructureRegistrationExtensions
{
    public static IRegistrationBuilder<TLimit, TActivatorData, TStyle>
        InstancePerWorkContext<TLimit, TActivatorData, TStyle>(
            this IRegistrationBuilder<TLimit, TActivatorData, TStyle> registration)
    {
        if (registration == null) throw new ArgumentNullException("registration");
        return registration.InstancePerMatchingLifetimeScope(InfrastructureModule.CONTEXT_TAG);
    }
}

Now, anywhere where we need to register something for the unit of work like the DbContext, we would call builder.Register(c => ...).InstancePerWorkContext();

Mission Accomplished

While you may not be using all of the same components together as I am, you may find yourself needing to use the same infrastructure outside of a http request and finding yourself stuck. Hopefully this will help you get through it.

IWorkContext
public interface IWorkContext
{
    T Resolve<T>();
    bool TryResolve<T>(out T service);
    object Resolve(Type type);
}
DefaultWorkContext
class DefaultWorkContext : IWorkContext
{
    private readonly IComponentContext Context;
    public DefaultWorkContext(IComponentContext context)
    {
        Context = context;
    }

    public T Resolve<T>()
    {
        return Context.Resolve<T>();
    }

    public bool TryResolve<T>(out T service)
    {
        return Context.TryResolve(out service);
    }

    public object Resolve(Type type)
    {
        return Context.Resolve(type);
    }
}
DefaultWorkContext
class DefaultWorkContextAccessor : IWorkContextAccessor, IDisposable
{
    static readonly object CONTEXT_KEY = new object();
    private readonly ILifetimeScope Lifetime;
    readonly ThreadLocal<IWorkContext> ThreadContext = new ThreadLocal<IWorkContext>();
    readonly object Tag;
    public DefaultWorkContextAccessor(object tag, ILifetimeScope lifetime)
    {
        Tag = tag;
        Lifetime = lifetime;
    }

    public IWorkContext GetContext()
    {
        var httpContext = HttpContext.Current;
        if (httpContext != null)
            return ResolveHttpContext(new HttpContextWrapper(httpContext));

        return ThreadContext.Value;
    }

    private IWorkContext ResolveHttpContext(HttpContextBase httpContext)
    {
        IWorkContext context = httpContext.Items[CONTEXT_KEY] as IWorkContext;
        if (context != null)
            return context;

        // find autofac managed lifetime
        ILifetimeScope lifetime = httpContext.Items[typeof(ILifetimeScope)] as ILifetimeScope;
        if (autofacManagedLifetime != null)
        {
            context = new DefaultWorkContext(lifetime);
            httpContext.Items[CONTEXT_KEY] = context;
            return context;
        }

        return null;
    }

    public IWorkContextScope CreateScope()
    {
        var workLifetime = Lifetime.BeginLifetimeScope(Tag);
        try
        {
            var httpContext = HttpContext.Current;
            if (httpContext != null)
                return new HttpWorkContextScope(workLifetime, new HttpContextWrapper(httpContext));

            return new ThreadLocalContextScope(workLifetime, ThreadContext);
        }
        catch
        {
            // if there is a problem, kill lifetime then rethrow
            workLifetime.Dispose();
            throw;
        }
    }

    public void Dispose()
    {
        ThreadContext.Dispose();
    }

    abstract class AbstractScope : IWorkContextScope
    {
        bool disposed = false;
        readonly IWorkContext WorkContext;
        readonly ILifetimeScope Scope;
        public AbstractScope(ILifetimeScope scope)
        {
            Scope = scope;
            WorkContext = scope.Resolve<IWorkContext>();
        }

        public IWorkContext Context { get { return WorkContext; } }

        public void Dispose()
        {
            if (disposed) return;

            disposed = true;
            using (Scope)
                OnDispose();
        }

        protected virtual void OnDispose()
        {
        }
    }

    class HttpWorkContextScope : AbstractScope
    {
        readonly HttpContextBase HttpContext;
        public HttpWorkContextScope(ILifetimeScope lifetime, HttpContextBase httpContext)
            : base(lifetime)
        {
            HttpContext = httpContext;
            HttpContext.Items[CONTEXT_KEY] = Context;
            lifetime.CurrentScopeEnding += delegate
            {
                HttpContext.Items.Remove(CONTEXT_KEY);
            };
        }
    }

    class ThreadLocalContextScope : AbstractScope
    {
        private readonly ThreadLocal<IWorkContext> ContextStorage;
        public ThreadLocalContextScope(ILifetimeScope scope, ThreadLocal<IWorkContext> contextStorage) : base(scope)
        {
            ContextStorage = contextStorage;
            ContextStorage.Value = Context;
        }

        protected override void OnDispose()
        {
            ContextStorage.Value = null;
        }
    }
}
IDependencyResolver and IDependencyScope (Web API)
class WorkContextApiDependencyResolver : IDependencyResolver
{
    private bool _disposed;
    readonly ILifetimeScope _container;
    readonly IWorkContextAccessor ContextAccessor;

    public WorkContextApiDependencyResolver(ILifetimeScope container)
    {
        if (container == null) throw new ArgumentNullException("container");
        _container = container;
        ContextAccessor = container.Resolve<IWorkContextAccessor>();
    }

    public ILifetimeScope Container { get { return _container; } }

    public object GetService(Type serviceType)
    {
        return Container.ResolveOptional(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        if (!Container.IsRegistered(serviceType))
            return Enumerable.Empty<object>();

        var enumerableServiceType = typeof(IEnumerable<>).MakeGenericType(serviceType);
        var instance = Container.Resolve(enumerableServiceType);
        return (IEnumerable<object>)instance;
    }

    public IDependencyScope BeginScope()
    {
        var scope = ContextAccessor.CreateScope();
        return new WorkContextApiDependencyScope(scope);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this._disposed)
        {
            if (disposing)
            {
                if (_container != null)
                    _container.Dispose();
            }
            this._disposed = true;
        }
    }
}

class WorkContextApiDependencyScope : IDependencyScope
{
    private bool _disposed;

    readonly ILifetimeScope _lifetimeScope;
    private readonly IWorkContextScope ContextScope;

    public WorkContextApiDependencyScope(IWorkContextScope contextScope)
    {
        if (contextScope == null) throw new ArgumentNullException("contextScope");

        ContextScope = contextScope;
        _lifetimeScope = contextScope.Resolve<ILifetimeScope>();
    }

    ~WorkContextApiDependencyScope()
    {
        Dispose(false);
    }

    public ILifetimeScope LifetimeScope { get { return _lifetimeScope; } }

    public object GetService(Type serviceType)
    {
        return _lifetimeScope.ResolveOptional(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        if (!_lifetimeScope.IsRegistered(serviceType))
            return Enumerable.Empty<object>();

        var enumerableServiceType = typeof(IEnumerable<>).MakeGenericType(serviceType);
        var instance = _lifetimeScope.Resolve(enumerableServiceType);
        return (IEnumerable<object>)instance;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (ContextScope != null)
                    ContextScope.Dispose();
            }
            _disposed = true;
        }
    }
}