TagHelper for class inlining

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
1
down vote

favorite
1












I'm building a RESTful service that sends emails with body rendered by partial views. It replaces my old solution that used hardcoded templates of IHtmlElement inside each application.



One of its features is to inline classes as style. I'm using class because it's easier to design a view this way. Since it's about emails, I'm not expecting any fency style selectors and to make it simple I'm using only classes.




InlineClassTagHelper



The inlining is driven by the InlineClassTagHelper. It stops at each element with the class attribute and checks if there are classes prefixed with m- (it's my custom prefix that stands for mail). It then looks for a style in the parsed .css file. Its name is always wwwroot/css/Conroller.Action.css. When found, it sets the style attribute and removes the class one.



[HtmlTargetElement(Attributes = "class")]
public class InlineClassTagHelper : TagHelper

private readonly CssProvider _cssProvider;

public InlineClassTagHelper(CssProvider cssProvider)

_cssProvider = cssProvider;


[HtmlAttributeNotBound, ViewContext]
public ViewContext ViewContext get; set;

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)

var classNames =
output
.Attributes["class"]
?.Value
.ToString()
.Split(new ' ' , StringSplitOptions.RemoveEmptyEntries);

if (classNames is null)

return;


var inlineableClassNames =
(from className in classNames
where className.StartsWith("m-")
select SoftString.Create(className)).ToImmutableHashSet();

if (inlineableClassNames.None())

return;


var cssFileName =
$"wwwroot/css/" +
$"ViewContext.RouteData.Values["controller"]." +
$"ViewContext.RouteData.Values["action"].css";

var css = await _cssProvider.GetCss(cssFileName);

var declarations =
from ruleset in css
from selector in ruleset.Selectors
join className in inlineableClassNames on selector equals className
select ruleset.Declarations.TrimEnd(';');

var style = declarations.Join("; ");

if (style.IsNullOrEmpty())

// Make debugging of missing styles easier by highlighting the element with a red border.
output.Attributes.SetAttribute("style", "border: 1px solid #ff6666; border-radius: 3px;");

else

output.Attributes.SetAttribute("style", style);
output.Attributes.RemoveAll("class");






Caching



To avoid multiple loads (for each class attribute occurance) the .css file is cached for the lifetime of a request and loaded by another helper service I call ICssProvider.



public interface ICssProvider

Task<Css> GetCss(string fileName);


public class CssProvider : ICssProvider

private readonly IFileProvider _fileProvider;

private Css _css;

public CssProvider(IFileProvider fileProvider)

_fileProvider = fileProvider;


public async Task<Css> GetCss(string fileName)

if (_css is null)

var cssFile = _fileProvider.GetFileInfo(fileName);
using (var reader = new StreamReader(cssFile.CreateReadStream()))

var cssString = await reader.ReadToEndAsync();
_css = CssParser.Default.Parse(cssString);
Debug.WriteLine($"fileName loaded.");



return _css;




This is registered in Startup as



services.AddScoped<CssProvider>();



Example



When the tag-helper finds an element like this one:






<h2 class="m-title">@ViewData["Title"]</h2>



it turns it into this:






<h2 style="color: blueviolet;">About</h2>



where the .css is:






m-title 
color: blueviolet;





Would you say it's a good solution or do you see any room for improvement?







share|improve this question

















  • 1




    Initial observation, assuming CssParser is static (tight coupling to static dependencies), is there any way to abstract that out and have it explicitly injected via constructor?
    – Nkosi
    Feb 24 at 17:14











  • @Nkosi you're right, it should be injected. I actually have an interface for it, it's the ICssParser and I am able to do it correctly, but I also have this Default property in case I'm too lazy for a full DI ;-)
    – t3chb0t
    Feb 24 at 18:46










  • The CSS provider can cache the filename mapped to the CSS in a dictionary. The current design uses the same one for all file names after the initial set.
    – Nkosi
    Feb 25 at 5:02











  • You also need to register the interface. You are only registering the implementation.
    – Nkosi
    Feb 25 at 5:06










  • @Nkosi I think this is ok because there is only one file per action and each action is one request, right? So for each request there will be only a single fine in the cache. That's what the Scoped service registration does. It is resolved one per request. And within a single request the tag-helper is created everytime it finds the class attribute but still this is the same request.
    – t3chb0t
    Feb 25 at 8:58
















up vote
1
down vote

favorite
1












I'm building a RESTful service that sends emails with body rendered by partial views. It replaces my old solution that used hardcoded templates of IHtmlElement inside each application.



One of its features is to inline classes as style. I'm using class because it's easier to design a view this way. Since it's about emails, I'm not expecting any fency style selectors and to make it simple I'm using only classes.




InlineClassTagHelper



The inlining is driven by the InlineClassTagHelper. It stops at each element with the class attribute and checks if there are classes prefixed with m- (it's my custom prefix that stands for mail). It then looks for a style in the parsed .css file. Its name is always wwwroot/css/Conroller.Action.css. When found, it sets the style attribute and removes the class one.



[HtmlTargetElement(Attributes = "class")]
public class InlineClassTagHelper : TagHelper

private readonly CssProvider _cssProvider;

public InlineClassTagHelper(CssProvider cssProvider)

_cssProvider = cssProvider;


[HtmlAttributeNotBound, ViewContext]
public ViewContext ViewContext get; set;

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)

var classNames =
output
.Attributes["class"]
?.Value
.ToString()
.Split(new ' ' , StringSplitOptions.RemoveEmptyEntries);

if (classNames is null)

return;


var inlineableClassNames =
(from className in classNames
where className.StartsWith("m-")
select SoftString.Create(className)).ToImmutableHashSet();

if (inlineableClassNames.None())

return;


var cssFileName =
$"wwwroot/css/" +
$"ViewContext.RouteData.Values["controller"]." +
$"ViewContext.RouteData.Values["action"].css";

var css = await _cssProvider.GetCss(cssFileName);

var declarations =
from ruleset in css
from selector in ruleset.Selectors
join className in inlineableClassNames on selector equals className
select ruleset.Declarations.TrimEnd(';');

var style = declarations.Join("; ");

if (style.IsNullOrEmpty())

// Make debugging of missing styles easier by highlighting the element with a red border.
output.Attributes.SetAttribute("style", "border: 1px solid #ff6666; border-radius: 3px;");

else

output.Attributes.SetAttribute("style", style);
output.Attributes.RemoveAll("class");






Caching



To avoid multiple loads (for each class attribute occurance) the .css file is cached for the lifetime of a request and loaded by another helper service I call ICssProvider.



public interface ICssProvider

Task<Css> GetCss(string fileName);


public class CssProvider : ICssProvider

private readonly IFileProvider _fileProvider;

private Css _css;

public CssProvider(IFileProvider fileProvider)

_fileProvider = fileProvider;


public async Task<Css> GetCss(string fileName)

if (_css is null)

var cssFile = _fileProvider.GetFileInfo(fileName);
using (var reader = new StreamReader(cssFile.CreateReadStream()))

var cssString = await reader.ReadToEndAsync();
_css = CssParser.Default.Parse(cssString);
Debug.WriteLine($"fileName loaded.");



return _css;




This is registered in Startup as



services.AddScoped<CssProvider>();



Example



When the tag-helper finds an element like this one:






<h2 class="m-title">@ViewData["Title"]</h2>



it turns it into this:






<h2 style="color: blueviolet;">About</h2>



where the .css is:






m-title 
color: blueviolet;





Would you say it's a good solution or do you see any room for improvement?







share|improve this question

















  • 1




    Initial observation, assuming CssParser is static (tight coupling to static dependencies), is there any way to abstract that out and have it explicitly injected via constructor?
    – Nkosi
    Feb 24 at 17:14











  • @Nkosi you're right, it should be injected. I actually have an interface for it, it's the ICssParser and I am able to do it correctly, but I also have this Default property in case I'm too lazy for a full DI ;-)
    – t3chb0t
    Feb 24 at 18:46










  • The CSS provider can cache the filename mapped to the CSS in a dictionary. The current design uses the same one for all file names after the initial set.
    – Nkosi
    Feb 25 at 5:02











  • You also need to register the interface. You are only registering the implementation.
    – Nkosi
    Feb 25 at 5:06










  • @Nkosi I think this is ok because there is only one file per action and each action is one request, right? So for each request there will be only a single fine in the cache. That's what the Scoped service registration does. It is resolved one per request. And within a single request the tag-helper is created everytime it finds the class attribute but still this is the same request.
    – t3chb0t
    Feb 25 at 8:58












up vote
1
down vote

favorite
1









up vote
1
down vote

favorite
1






1





I'm building a RESTful service that sends emails with body rendered by partial views. It replaces my old solution that used hardcoded templates of IHtmlElement inside each application.



One of its features is to inline classes as style. I'm using class because it's easier to design a view this way. Since it's about emails, I'm not expecting any fency style selectors and to make it simple I'm using only classes.




InlineClassTagHelper



The inlining is driven by the InlineClassTagHelper. It stops at each element with the class attribute and checks if there are classes prefixed with m- (it's my custom prefix that stands for mail). It then looks for a style in the parsed .css file. Its name is always wwwroot/css/Conroller.Action.css. When found, it sets the style attribute and removes the class one.



[HtmlTargetElement(Attributes = "class")]
public class InlineClassTagHelper : TagHelper

private readonly CssProvider _cssProvider;

public InlineClassTagHelper(CssProvider cssProvider)

_cssProvider = cssProvider;


[HtmlAttributeNotBound, ViewContext]
public ViewContext ViewContext get; set;

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)

var classNames =
output
.Attributes["class"]
?.Value
.ToString()
.Split(new ' ' , StringSplitOptions.RemoveEmptyEntries);

if (classNames is null)

return;


var inlineableClassNames =
(from className in classNames
where className.StartsWith("m-")
select SoftString.Create(className)).ToImmutableHashSet();

if (inlineableClassNames.None())

return;


var cssFileName =
$"wwwroot/css/" +
$"ViewContext.RouteData.Values["controller"]." +
$"ViewContext.RouteData.Values["action"].css";

var css = await _cssProvider.GetCss(cssFileName);

var declarations =
from ruleset in css
from selector in ruleset.Selectors
join className in inlineableClassNames on selector equals className
select ruleset.Declarations.TrimEnd(';');

var style = declarations.Join("; ");

if (style.IsNullOrEmpty())

// Make debugging of missing styles easier by highlighting the element with a red border.
output.Attributes.SetAttribute("style", "border: 1px solid #ff6666; border-radius: 3px;");

else

output.Attributes.SetAttribute("style", style);
output.Attributes.RemoveAll("class");






Caching



To avoid multiple loads (for each class attribute occurance) the .css file is cached for the lifetime of a request and loaded by another helper service I call ICssProvider.



public interface ICssProvider

Task<Css> GetCss(string fileName);


public class CssProvider : ICssProvider

private readonly IFileProvider _fileProvider;

private Css _css;

public CssProvider(IFileProvider fileProvider)

_fileProvider = fileProvider;


public async Task<Css> GetCss(string fileName)

if (_css is null)

var cssFile = _fileProvider.GetFileInfo(fileName);
using (var reader = new StreamReader(cssFile.CreateReadStream()))

var cssString = await reader.ReadToEndAsync();
_css = CssParser.Default.Parse(cssString);
Debug.WriteLine($"fileName loaded.");



return _css;




This is registered in Startup as



services.AddScoped<CssProvider>();



Example



When the tag-helper finds an element like this one:






<h2 class="m-title">@ViewData["Title"]</h2>



it turns it into this:






<h2 style="color: blueviolet;">About</h2>



where the .css is:






m-title 
color: blueviolet;





Would you say it's a good solution or do you see any room for improvement?







share|improve this question













I'm building a RESTful service that sends emails with body rendered by partial views. It replaces my old solution that used hardcoded templates of IHtmlElement inside each application.



One of its features is to inline classes as style. I'm using class because it's easier to design a view this way. Since it's about emails, I'm not expecting any fency style selectors and to make it simple I'm using only classes.




InlineClassTagHelper



The inlining is driven by the InlineClassTagHelper. It stops at each element with the class attribute and checks if there are classes prefixed with m- (it's my custom prefix that stands for mail). It then looks for a style in the parsed .css file. Its name is always wwwroot/css/Conroller.Action.css. When found, it sets the style attribute and removes the class one.



[HtmlTargetElement(Attributes = "class")]
public class InlineClassTagHelper : TagHelper

private readonly CssProvider _cssProvider;

public InlineClassTagHelper(CssProvider cssProvider)

_cssProvider = cssProvider;


[HtmlAttributeNotBound, ViewContext]
public ViewContext ViewContext get; set;

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)

var classNames =
output
.Attributes["class"]
?.Value
.ToString()
.Split(new ' ' , StringSplitOptions.RemoveEmptyEntries);

if (classNames is null)

return;


var inlineableClassNames =
(from className in classNames
where className.StartsWith("m-")
select SoftString.Create(className)).ToImmutableHashSet();

if (inlineableClassNames.None())

return;


var cssFileName =
$"wwwroot/css/" +
$"ViewContext.RouteData.Values["controller"]." +
$"ViewContext.RouteData.Values["action"].css";

var css = await _cssProvider.GetCss(cssFileName);

var declarations =
from ruleset in css
from selector in ruleset.Selectors
join className in inlineableClassNames on selector equals className
select ruleset.Declarations.TrimEnd(';');

var style = declarations.Join("; ");

if (style.IsNullOrEmpty())

// Make debugging of missing styles easier by highlighting the element with a red border.
output.Attributes.SetAttribute("style", "border: 1px solid #ff6666; border-radius: 3px;");

else

output.Attributes.SetAttribute("style", style);
output.Attributes.RemoveAll("class");






Caching



To avoid multiple loads (for each class attribute occurance) the .css file is cached for the lifetime of a request and loaded by another helper service I call ICssProvider.



public interface ICssProvider

Task<Css> GetCss(string fileName);


public class CssProvider : ICssProvider

private readonly IFileProvider _fileProvider;

private Css _css;

public CssProvider(IFileProvider fileProvider)

_fileProvider = fileProvider;


public async Task<Css> GetCss(string fileName)

if (_css is null)

var cssFile = _fileProvider.GetFileInfo(fileName);
using (var reader = new StreamReader(cssFile.CreateReadStream()))

var cssString = await reader.ReadToEndAsync();
_css = CssParser.Default.Parse(cssString);
Debug.WriteLine($"fileName loaded.");



return _css;




This is registered in Startup as



services.AddScoped<CssProvider>();



Example



When the tag-helper finds an element like this one:






<h2 class="m-title">@ViewData["Title"]</h2>



it turns it into this:






<h2 style="color: blueviolet;">About</h2>



where the .css is:






m-title 
color: blueviolet;





Would you say it's a good solution or do you see any room for improvement?









share|improve this question












share|improve this question




share|improve this question








edited Feb 24 at 22:31
























asked Feb 24 at 11:44









t3chb0t

32.1k54195




32.1k54195







  • 1




    Initial observation, assuming CssParser is static (tight coupling to static dependencies), is there any way to abstract that out and have it explicitly injected via constructor?
    – Nkosi
    Feb 24 at 17:14











  • @Nkosi you're right, it should be injected. I actually have an interface for it, it's the ICssParser and I am able to do it correctly, but I also have this Default property in case I'm too lazy for a full DI ;-)
    – t3chb0t
    Feb 24 at 18:46










  • The CSS provider can cache the filename mapped to the CSS in a dictionary. The current design uses the same one for all file names after the initial set.
    – Nkosi
    Feb 25 at 5:02











  • You also need to register the interface. You are only registering the implementation.
    – Nkosi
    Feb 25 at 5:06










  • @Nkosi I think this is ok because there is only one file per action and each action is one request, right? So for each request there will be only a single fine in the cache. That's what the Scoped service registration does. It is resolved one per request. And within a single request the tag-helper is created everytime it finds the class attribute but still this is the same request.
    – t3chb0t
    Feb 25 at 8:58












  • 1




    Initial observation, assuming CssParser is static (tight coupling to static dependencies), is there any way to abstract that out and have it explicitly injected via constructor?
    – Nkosi
    Feb 24 at 17:14











  • @Nkosi you're right, it should be injected. I actually have an interface for it, it's the ICssParser and I am able to do it correctly, but I also have this Default property in case I'm too lazy for a full DI ;-)
    – t3chb0t
    Feb 24 at 18:46










  • The CSS provider can cache the filename mapped to the CSS in a dictionary. The current design uses the same one for all file names after the initial set.
    – Nkosi
    Feb 25 at 5:02











  • You also need to register the interface. You are only registering the implementation.
    – Nkosi
    Feb 25 at 5:06










  • @Nkosi I think this is ok because there is only one file per action and each action is one request, right? So for each request there will be only a single fine in the cache. That's what the Scoped service registration does. It is resolved one per request. And within a single request the tag-helper is created everytime it finds the class attribute but still this is the same request.
    – t3chb0t
    Feb 25 at 8:58







1




1




Initial observation, assuming CssParser is static (tight coupling to static dependencies), is there any way to abstract that out and have it explicitly injected via constructor?
– Nkosi
Feb 24 at 17:14





Initial observation, assuming CssParser is static (tight coupling to static dependencies), is there any way to abstract that out and have it explicitly injected via constructor?
– Nkosi
Feb 24 at 17:14













@Nkosi you're right, it should be injected. I actually have an interface for it, it's the ICssParser and I am able to do it correctly, but I also have this Default property in case I'm too lazy for a full DI ;-)
– t3chb0t
Feb 24 at 18:46




@Nkosi you're right, it should be injected. I actually have an interface for it, it's the ICssParser and I am able to do it correctly, but I also have this Default property in case I'm too lazy for a full DI ;-)
– t3chb0t
Feb 24 at 18:46












The CSS provider can cache the filename mapped to the CSS in a dictionary. The current design uses the same one for all file names after the initial set.
– Nkosi
Feb 25 at 5:02





The CSS provider can cache the filename mapped to the CSS in a dictionary. The current design uses the same one for all file names after the initial set.
– Nkosi
Feb 25 at 5:02













You also need to register the interface. You are only registering the implementation.
– Nkosi
Feb 25 at 5:06




You also need to register the interface. You are only registering the implementation.
– Nkosi
Feb 25 at 5:06












@Nkosi I think this is ok because there is only one file per action and each action is one request, right? So for each request there will be only a single fine in the cache. That's what the Scoped service registration does. It is resolved one per request. And within a single request the tag-helper is created everytime it finds the class attribute but still this is the same request.
– t3chb0t
Feb 25 at 8:58




@Nkosi I think this is ok because there is only one file per action and each action is one request, right? So for each request there will be only a single fine in the cache. That's what the Scoped service registration does. It is resolved one per request. And within a single request the tag-helper is created everytime it finds the class attribute but still this is the same request.
– t3chb0t
Feb 25 at 8:58










1 Answer
1






active

oldest

votes

















up vote
2
down vote



accepted










Initial observation, assuming CssParser was static (tight coupling to static dependencies), was to abstract that core functionality out into its own concern and have it explicitly injected via constructor.



That was done. However, the extracted helper service implementation was registered like



services.AddScoped<CssProvider>();


While it works, for a more SOLID design, classes should depend on abstractions and not concretions.



An abstraction exists, so I suggest refactoring the helper to explicitly depend on ICssProvider abstraction



//...code removed for brevity

private readonly ICssProvider _cssProvider;

public InlineClassTagHelper(ICssProvider cssProvider)
_cssProvider = cssProvider;


//...code removed for brevity


and register the abstraction with its implementation in the composition root
(Startup) with the following extension



services.AddScoped<ICssProvider, CssProvider>();





share|improve this answer























  • Oh, I don't know how I missed that it takes two generic parameters! :-o
    – t3chb0t
    Feb 27 at 4:58










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188262%2ftaghelper-for-class-inlining%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted










Initial observation, assuming CssParser was static (tight coupling to static dependencies), was to abstract that core functionality out into its own concern and have it explicitly injected via constructor.



That was done. However, the extracted helper service implementation was registered like



services.AddScoped<CssProvider>();


While it works, for a more SOLID design, classes should depend on abstractions and not concretions.



An abstraction exists, so I suggest refactoring the helper to explicitly depend on ICssProvider abstraction



//...code removed for brevity

private readonly ICssProvider _cssProvider;

public InlineClassTagHelper(ICssProvider cssProvider)
_cssProvider = cssProvider;


//...code removed for brevity


and register the abstraction with its implementation in the composition root
(Startup) with the following extension



services.AddScoped<ICssProvider, CssProvider>();





share|improve this answer























  • Oh, I don't know how I missed that it takes two generic parameters! :-o
    – t3chb0t
    Feb 27 at 4:58














up vote
2
down vote



accepted










Initial observation, assuming CssParser was static (tight coupling to static dependencies), was to abstract that core functionality out into its own concern and have it explicitly injected via constructor.



That was done. However, the extracted helper service implementation was registered like



services.AddScoped<CssProvider>();


While it works, for a more SOLID design, classes should depend on abstractions and not concretions.



An abstraction exists, so I suggest refactoring the helper to explicitly depend on ICssProvider abstraction



//...code removed for brevity

private readonly ICssProvider _cssProvider;

public InlineClassTagHelper(ICssProvider cssProvider)
_cssProvider = cssProvider;


//...code removed for brevity


and register the abstraction with its implementation in the composition root
(Startup) with the following extension



services.AddScoped<ICssProvider, CssProvider>();





share|improve this answer























  • Oh, I don't know how I missed that it takes two generic parameters! :-o
    – t3chb0t
    Feb 27 at 4:58












up vote
2
down vote



accepted







up vote
2
down vote



accepted






Initial observation, assuming CssParser was static (tight coupling to static dependencies), was to abstract that core functionality out into its own concern and have it explicitly injected via constructor.



That was done. However, the extracted helper service implementation was registered like



services.AddScoped<CssProvider>();


While it works, for a more SOLID design, classes should depend on abstractions and not concretions.



An abstraction exists, so I suggest refactoring the helper to explicitly depend on ICssProvider abstraction



//...code removed for brevity

private readonly ICssProvider _cssProvider;

public InlineClassTagHelper(ICssProvider cssProvider)
_cssProvider = cssProvider;


//...code removed for brevity


and register the abstraction with its implementation in the composition root
(Startup) with the following extension



services.AddScoped<ICssProvider, CssProvider>();





share|improve this answer















Initial observation, assuming CssParser was static (tight coupling to static dependencies), was to abstract that core functionality out into its own concern and have it explicitly injected via constructor.



That was done. However, the extracted helper service implementation was registered like



services.AddScoped<CssProvider>();


While it works, for a more SOLID design, classes should depend on abstractions and not concretions.



An abstraction exists, so I suggest refactoring the helper to explicitly depend on ICssProvider abstraction



//...code removed for brevity

private readonly ICssProvider _cssProvider;

public InlineClassTagHelper(ICssProvider cssProvider)
_cssProvider = cssProvider;


//...code removed for brevity


and register the abstraction with its implementation in the composition root
(Startup) with the following extension



services.AddScoped<ICssProvider, CssProvider>();






share|improve this answer















share|improve this answer



share|improve this answer








edited Feb 27 at 4:10


























answered Feb 27 at 2:30









Nkosi

1,870619




1,870619











  • Oh, I don't know how I missed that it takes two generic parameters! :-o
    – t3chb0t
    Feb 27 at 4:58
















  • Oh, I don't know how I missed that it takes two generic parameters! :-o
    – t3chb0t
    Feb 27 at 4:58















Oh, I don't know how I missed that it takes two generic parameters! :-o
– t3chb0t
Feb 27 at 4:58




Oh, I don't know how I missed that it takes two generic parameters! :-o
– t3chb0t
Feb 27 at 4:58












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188262%2ftaghelper-for-class-inlining%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation