diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml
index dd078acc..ff19362b 100644
--- a/.github/workflows/dotnetcore.yml
+++ b/.github/workflows/dotnetcore.yml
@@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v4.2.2
- name: Setup .NET Core
- uses: actions/setup-dotnet@v4.1.0
+ uses: actions/setup-dotnet@v4.3.1
with:
dotnet-version: 3.1.101
- name: Install dependencies
diff --git a/PreMailer.Net/Benchmarks/Benchmarks.csproj b/PreMailer.Net/Benchmarks/Benchmarks.csproj
index 4731aa33..22100bf2 100644
--- a/PreMailer.Net/Benchmarks/Benchmarks.csproj
+++ b/PreMailer.Net/Benchmarks/Benchmarks.csproj
@@ -2,12 +2,12 @@
Exe
- net7.0
+ net9.0
enable
-
+
diff --git a/PreMailer.Net/Benchmarks/Program.cs b/PreMailer.Net/Benchmarks/Program.cs
index f04d91e8..025fffd2 100644
--- a/PreMailer.Net/Benchmarks/Program.cs
+++ b/PreMailer.Net/Benchmarks/Program.cs
@@ -1,17 +1,30 @@
using AngleSharp;
using AngleSharp.Html.Parser;
using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
public static class Program
{
public static void Main()
{
- BenchmarkRunner.Run();
+ // Some local environments may run into issues with Windows Defender or
+ // SentinelOne (and others) when running a benchmark. This ensures we
+ // keep our toolchain within our process and stops the above apps from blocking
+ // our benchmark process, but can slow the execution time.
+ var avSafeConfig = DefaultConfig.Instance
+ .AddJob(
+ Job.ShortRun
+ .WithToolchain(InProcessNoEmitToolchain.Instance)
+ .WithIterationCount(100)
+ );
+
+ BenchmarkRunner.Run(avSafeConfig);
}
}
-[SimpleJob(invocationCount: 100, iterationCount: 100)]
[MemoryDiagnoser]
public class Realistic
{
@@ -47,7 +60,7 @@ public void MoveCssInline_AllFlags()
PreMailer Benchmark
-
+
diff --git a/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs b/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs
index b5523773..9665d98b 100644
--- a/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs
+++ b/PreMailer.Net/PreMailer.Net.Tests/CssStyleEquivalenceTests.cs
@@ -17,7 +17,7 @@ public void FindEquivalentStyles()
var result = CssStyleEquivalence.FindEquivalent(nodewithoutselector, clazz);
- Assert.Equal(1, result.Count);
+ Assert.Single(result);
}
[Fact]
@@ -32,7 +32,7 @@ public void FindEquivalentStylesNoMatchingStyles()
var result = CssStyleEquivalence.FindEquivalent(nodewithoutselector, clazz);
- Assert.Equal(0, result.Count);
+ Assert.Empty(result);
}
}
}
\ No newline at end of file
diff --git a/PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs b/PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs
new file mode 100644
index 00000000..0b9d1854
--- /dev/null
+++ b/PreMailer.Net/PreMailer.Net.Tests/ImportRuleCssSourceTests.cs
@@ -0,0 +1,217 @@
+using Xunit;
+using Moq;
+using PreMailer.Net.Downloaders;
+using PreMailer.Net.Sources;
+using System;
+using System.Text;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PreMailer.Net.Tests
+{
+ public class ImportRuleCssSourceTests
+ {
+ private readonly Mock _webDownloader = new Mock();
+
+ public ImportRuleCssSourceTests()
+ {
+ WebDownloader.SharedDownloader = _webDownloader.Object;
+ }
+
+ [Fact]
+ public void ItShould_DownloadAllImportedUrls_WhenCssContainsImportRules()
+ {
+ var baseUri = new Uri("https://a.com");
+ var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" };
+
+ var css = CreateCss(urls);
+ var sut = new ImportRuleCssSource();
+
+ sut.GetCss(baseUri, css);
+
+ foreach (var url in urls)
+ {
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.Equals(new Uri(baseUri, url)))));
+ }
+ }
+
+ [Fact]
+ public void ItShould_NotDownloadUrls_WhenLevelIsGreaterThanTwo()
+ {
+ var baseUri = new Uri("https://a.com");
+ var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" };
+
+ var css = CreateCss(urls);
+ var sut = new ImportRuleCssSource();
+
+ sut.GetCss(baseUri, css, 2);
+
+ _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public void ItShould_NotDownloadUrls_WhenCssIsEmpty()
+ {
+ var baseUri = new Uri("https://a.com");
+ var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" };
+
+ var css = CreateCss(urls);
+ var sut = new ImportRuleCssSource();
+
+ sut.GetCss(baseUri, string.Empty);
+
+ _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public void ItShould_NotDownloadUrls_WhenCssIsNull()
+ {
+ var baseUri = new Uri("https://a.com");
+ var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" };
+
+ var css = CreateCss(urls);
+ var sut = new ImportRuleCssSource();
+
+ sut.GetCss(baseUri, null);
+
+ _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public void ItShould_NotDownloadUrls_WhenCssDoesNotContainImportRules()
+ {
+ var baseUri = new Uri("https://a.com");
+ var urls = new List() { "variables.css?v234", "/fonts.css", "https://fonts.google.com/css/test-font" };
+
+ var css = string.Join(Environment.NewLine, urls);
+ var sut = new ImportRuleCssSource();
+
+ sut.GetCss(baseUri, css);
+
+ _webDownloader.Verify(w => w.DownloadString(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public void ItShould_LoadImportsRecursively_UntilLevelTwo()
+ {
+ // Arrange
+ var baseUri = new Uri("https://a.com");
+ var level0Css = "@import \"level1.css\";";
+ var level1Css = "@import \"level2.css\";";
+ var level2Css = "@import \"level3.css\";";
+
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level1.css")))
+ .Returns(level1Css);
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level2.css")))
+ .Returns(level2Css);
+
+ var sut = new ImportRuleCssSource();
+
+ // Act
+ var result = sut.GetCss(baseUri, level0Css).ToList();
+
+ // Assert
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level1.css")), Times.Once);
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level2.css")), Times.Once);
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/level3.css")), Times.Never);
+ Assert.Equal(2, result.Count); // Should only contain level1.css and level2.css contents
+ }
+
+ [Fact]
+ public void ItShould_CacheDownloadedImports_AndNotDownloadTwice()
+ {
+ // Arrange
+ var baseUri = new Uri("https://a.com");
+ var css = "@import \"shared.css\"; @import \"also-shared.css\";";
+ var secondCss = "@import \"shared.css\";"; // References same file
+
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/shared.css")))
+ .Returns("h1 { color: red; }");
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/also-shared.css")))
+ .Returns("h2 { color: blue; }");
+
+ var sut = new ImportRuleCssSource();
+
+ // Act
+ var firstResult = sut.GetCss(baseUri, css);
+ var secondResult = sut.GetCss(baseUri, secondCss);
+
+ // Assert
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/shared.css")), Times.Once);
+ }
+
+ [Fact]
+ public void ItShould_PreserveImportOrder_WhenProcessingImports()
+ {
+ // Arrange
+ var baseUri = new Uri("https://a.com");
+ var css = "@import \"first.css\"; @import \"second.css\"; @import \"third.css\";";
+ var firstCss = "first { order: 1; }";
+ var secondCss = "second { order: 2; }";
+ var thirdCss = "third { order: 3; }";
+
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/first.css")))
+ .Returns(firstCss);
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/second.css")))
+ .Returns(secondCss);
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/third.css")))
+ .Returns(thirdCss);
+
+ var sut = new ImportRuleCssSource();
+
+ // Act
+ var result = sut.GetCss(baseUri, css).ToList();
+
+ // Assert
+ Assert.Equal(3, result.Count);
+ Assert.Equal(firstCss, result[0]);
+ Assert.Equal(secondCss, result[1]);
+ Assert.Equal(thirdCss, result[2]);
+ }
+
+ [Fact]
+ public void ItShould_HandleCircularImports_WithoutInfiniteLoop()
+ {
+ // Arrange
+ var baseUri = new Uri("https://a.com");
+ var css = "@import \"circular1.css\";";
+ var circular1Css = "@import \"circular2.css\";";
+ var circular2Css = "@import \"circular1.css\";"; // Creates a circle
+
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular1.css")))
+ .Returns(circular1Css);
+ _webDownloader
+ .Setup(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular2.css")))
+ .Returns(circular2Css);
+
+ var sut = new ImportRuleCssSource();
+
+ // Act
+ var result = sut.GetCss(baseUri, css).ToList();
+
+ // Assert
+ Assert.Equal(2, result.Count); // Should contain both files exactly once
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular1.css")), Times.Once);
+ _webDownloader.Verify(w => w.DownloadString(It.Is(u => u.ToString() == "https://a.com/circular2.css")), Times.Once);
+ }
+
+ private string CreateCss(IEnumerable imports)
+ {
+ var builder = new StringBuilder();
+ foreach (var import in imports)
+ {
+ builder.AppendLine($"@import \"{import}\";");
+ }
+
+ return builder.ToString();
+ }
+ }
+}
diff --git a/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj b/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj
index 8f975c7e..f6ea2bcd 100644
--- a/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj
+++ b/PreMailer.Net/PreMailer.Net.Tests/PreMailer.Net.Tests.csproj
@@ -1,17 +1,17 @@
- net7.0
+ net9.0
false
-
-
-
-
-
+
+
+
+
+
diff --git a/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs b/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs
index c34e915a..4db0e0e8 100644
--- a/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs
+++ b/PreMailer.Net/PreMailer.Net.Tests/PreMailerTests.cs
@@ -104,7 +104,7 @@ public void MoveCssInline_CrazyCssSelector_DoesNotThrowError()
}
catch (Exception ex)
{
- Assert.True(false, ex.Message);
+ Assert.Fail(ex.Message);
}
}
diff --git a/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs b/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs
index 281e7f33..d04de4d7 100644
--- a/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs
+++ b/PreMailer.Net/PreMailer.Net/Downloaders/WebDownloader.cs
@@ -1,3 +1,4 @@
+using PreMailer.Net.Extensions;
using System;
using System.IO;
using System.Net;
@@ -8,7 +9,7 @@ namespace PreMailer.Net.Downloaders
public class WebDownloader : IWebDownloader
{
private static IWebDownloader _sharedDownloader;
-
+ private const string CssMimeType = "text/css";
public static IWebDownloader SharedDownloader
{
get
@@ -29,8 +30,16 @@ public static IWebDownloader SharedDownloader
public string DownloadString(Uri uri)
{
var request = WebRequest.Create(uri);
+ request.Headers.Add(HttpRequestHeader.Accept, CssMimeType);
+
using (var response = request.GetResponse())
{
+ // We only support this operation for CSS file/content types coming back
+ // from the response. If we get something different, throw with the unsupported
+ // content type in the message
+ if(response.ParseContentType() != CssMimeType)
+ throw new NotSupportedException($"The Uri type is giving a response in unsupported content type '{response.ContentType}'.");
+
switch (response)
{
case HttpWebResponse httpWebResponse:
diff --git a/PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs b/PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs
new file mode 100644
index 00000000..5508a35d
--- /dev/null
+++ b/PreMailer.Net/PreMailer.Net/Extensions/WebResponseExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Net;
+
+namespace PreMailer.Net.Extensions
+{
+ public static class WebResponseExtensions
+ {
+ public static string ParseContentType(this WebResponse response)
+ {
+ if(response == null)
+ throw new NullReferenceException("Malformed response detected when parsing WebResponse Content-Type");
+
+ if(string.IsNullOrEmpty(response.ContentType))
+ throw new NullReferenceException("Malformed Content-Type response detected when parsing WebResponse");
+
+ var results = response.ContentType.Split(';');
+
+ if(results.Length == 0)
+ throw new FormatException("Malformed Content-Type response detected when parsing WebResponse");
+
+ return results[0];
+ }
+ }
+}
diff --git a/PreMailer.Net/PreMailer.Net/PreMailer.cs b/PreMailer.Net/PreMailer.Net/PreMailer.cs
index 5621249f..34a402c5 100644
--- a/PreMailer.Net/PreMailer.Net/PreMailer.cs
+++ b/PreMailer.Net/PreMailer.Net/PreMailer.cs
@@ -247,7 +247,7 @@ private bool DomainMatch(string domain, string url)
///
private IEnumerable GetCssBlocks(IEnumerable cssSources)
{
- return cssSources.Select(styleSource => styleSource.GetCss()).ToList();
+ return cssSources.SelectMany(styleSource => styleSource.GetCss()).ToList();
}
private void RemoveCssComments(IEnumerable cssSourceNodes)
@@ -405,7 +405,7 @@ private Dictionary> FindElementsWithStyles(
Selector = selectorParser.ParseSelector(x.Value.Name)
}).Where(x => x.Selector != null).ToList();
- foreach (var el in _document.DescendentsAndSelf())
+ foreach (var el in _document.DescendantsAndSelf())
{
foreach (var style in styles)
{
@@ -516,7 +516,7 @@ private int GetSelectorSpecificity(Dictionary cache, string selecto
private void RemoveHtmlComments()
{
- var comments = _document.Descendents().ToList();
+ var comments = _document.Descendants().ToList();
foreach (var comment in comments)
{
diff --git a/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs
index 4b263b8f..6b3f9861 100644
--- a/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs
+++ b/PreMailer.Net/PreMailer.Net/Sources/DocumentStyleTagCssSource.cs
@@ -1,4 +1,5 @@
-using AngleSharp.Dom;
+using System.Collections.Generic;
+using AngleSharp.Dom;
using PreMailer.Net.Extensions;
namespace PreMailer.Net.Sources
@@ -12,9 +13,9 @@ public DocumentStyleTagCssSource(IElement node)
_node = node;
}
- public string GetCss()
+ public IEnumerable GetCss()
{
- return _node.GetFirstTextNodeData() ?? "";
+ return [_node.GetFirstTextNodeData() ?? ""];
}
}
}
\ No newline at end of file
diff --git a/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs
index db67824f..43cc1d1c 100644
--- a/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs
+++ b/PreMailer.Net/PreMailer.Net/Sources/ICssSource.cs
@@ -1,10 +1,12 @@
-namespace PreMailer.Net.Sources
+using System.Collections.Generic;
+
+namespace PreMailer.Net.Sources
{
///
/// Arbitrary source of CSS code/definitions.
///
public interface ICssSource
{
- string GetCss();
+ IEnumerable GetCss();
}
}
\ No newline at end of file
diff --git a/PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs
new file mode 100644
index 00000000..0217f611
--- /dev/null
+++ b/PreMailer.Net/PreMailer.Net/Sources/ImportRuleCssSource.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text.RegularExpressions;
+using PreMailer.Net.Downloaders;
+
+namespace PreMailer.Net.Sources
+{
+ ///
+ /// This class is used by the LinkTagCssSource class for downloading/fetching import rules.
+ ///
+ public class ImportRuleCssSource
+ {
+ private Dictionary _importList = new Dictionary();
+ private static Regex _importRegex = new Regex("@import.*?[\"'](?[^\"']+)[\"'].*?;", RegexOptions.Multiline | RegexOptions.IgnoreCase);
+
+ public IEnumerable GetCss(Uri linkedStylesheetUrl, string contents, int level = 0)
+ {
+ if (level >= 2 || string.IsNullOrEmpty(contents))
+ {
+ return _importList.Values;
+ }
+
+ var baseUri = GetBaseUri(linkedStylesheetUrl);
+ var matches = GetMatches(contents);
+
+ foreach (Match match in matches)
+ {
+ var href = match.Groups["href"].Value;
+ Uri url = default;
+
+ if (Uri.IsWellFormedUriString(href, UriKind.Relative))
+ {
+ url = new Uri(baseUri, href);
+ }
+ else
+ {
+ url = new Uri(href);
+ }
+
+ if (!_importList.ContainsKey(url))
+ {
+ var content = DownloadContents(url);
+
+ _importList.Add(url, content);
+
+ GetCss(url, content, level + 1);
+ }
+ }
+
+ return _importList.Values;
+ }
+
+ private string DownloadContents(Uri downloadUri)
+ {
+ string contents;
+
+ try
+ {
+ contents = WebDownloader.SharedDownloader.DownloadString(downloadUri);
+ }
+ catch (WebException ex)
+ {
+ throw new WebException($"PreMailer.Net is unable to download the requested URL: {downloadUri}", ex);
+ }
+
+ return contents;
+ }
+
+ private static bool IsSupported(string scheme) => scheme == "http" || scheme == "https";
+
+ private static MatchCollection GetMatches(string contents)
+ {
+ if (string.IsNullOrEmpty(contents))
+ {
+ return default;
+ }
+
+ return _importRegex.Matches(contents);
+ }
+
+ private static Uri GetBaseUri(Uri downloadUri)
+ {
+ var baseUrl = new UriBuilder(downloadUri)
+ {
+ Port = -1 /* Excludes the port number */,
+ Query = string.Empty
+ };
+
+ // Strip of the css file segment
+ var path = baseUrl.Path;
+ baseUrl.Path = path.Substring(0, path.LastIndexOf('/') + 1);
+
+ return baseUrl.Uri;
+ }
+ }
+}
diff --git a/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs
index 308fe27b..669bdc83 100644
--- a/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs
+++ b/PreMailer.Net/PreMailer.Net/Sources/LinkTagCssSource.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Net;
using AngleSharp.Dom;
@@ -9,10 +10,16 @@ namespace PreMailer.Net.Sources
public class LinkTagCssSource : ICssSource
{
private readonly Uri _downloadUri;
- private string _cssContents;
+ private List _cssContents;
+ private ImportRuleCssSource _importRuleCssSource;
+ public LinkTagCssSource(IElement node, Uri baseUri) : this(node, baseUri, new ImportRuleCssSource())
+ {
+ }
- public LinkTagCssSource(IElement node, Uri baseUri)
+ public LinkTagCssSource(IElement node, Uri baseUri, ImportRuleCssSource importRuleCssSource)
{
+ _importRuleCssSource = importRuleCssSource;
+
// There must be an href
var href = node.Attributes.First(a => a.Name.Equals("href", StringComparison.OrdinalIgnoreCase)).Value;
@@ -27,24 +34,40 @@ public LinkTagCssSource(IElement node, Uri baseUri)
}
}
- public string GetCss()
+ public IEnumerable GetCss()
{
- Console.WriteLine($"GetCss scheme: {_downloadUri.Scheme}");
-
if (IsSupported(_downloadUri.Scheme))
{
- try
- {
- Console.WriteLine($"Will download from '{_downloadUri}' using {WebDownloader.SharedDownloader.GetType()}");
-
- return _cssContents ?? (_cssContents = WebDownloader.SharedDownloader.DownloadString(_downloadUri));
- }
- catch (WebException)
- {
- throw new WebException($"PreMailer.Net is unable to fetch the requested URL: {_downloadUri}");
- }
+ return _cssContents ?? DownloadContents();
+ }
+ return default;
+ }
+
+ private List DownloadContents()
+ {
+ string content;
+ _cssContents ??= new();
+
+ try
+ {
+ content = WebDownloader.SharedDownloader.DownloadString(_downloadUri);
+ }
+ catch (WebException ex)
+ {
+ throw new WebException($"PreMailer.Net is unable to download the requested URL: {_downloadUri}", ex);
}
- return string.Empty;
+
+ // Fetch possible import rules
+ var imports = _importRuleCssSource.GetCss(_downloadUri, content);
+
+ if (imports != null)
+ {
+ _cssContents.AddRange(imports);
+ }
+
+ _cssContents.Add(content);
+
+ return _cssContents;
}
private static bool IsSupported(string scheme)
diff --git a/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs b/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs
index ae5df3d8..39fbf163 100644
--- a/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs
+++ b/PreMailer.Net/PreMailer.Net/Sources/StringCssSource.cs
@@ -1,4 +1,4 @@
-// No usings needed
+using System.Collections.Generic;
namespace PreMailer.Net.Sources
{
@@ -11,9 +11,9 @@ public StringCssSource(string css)
this._css = css;
}
- public string GetCss()
+ public IEnumerable GetCss()
{
- return _css;
+ return [_css];
}
}
}
\ No newline at end of file