<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dennis Tretyakov</title>
    <description>The latest articles on DEV Community by Dennis Tretyakov (@tretyakovd).</description>
    <link>https://dev.to/tretyakovd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F371479%2F9ba03ce5-e942-4d06-92fa-f8730898f206.jpeg</url>
      <title>DEV Community: Dennis Tretyakov</title>
      <link>https://dev.to/tretyakovd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tretyakovd"/>
    <language>en</language>
    <item>
      <title>ID Token validation in dotnet</title>
      <dc:creator>Dennis Tretyakov</dc:creator>
      <pubDate>Wed, 24 Aug 2022 11:17:23 +0000</pubDate>
      <link>https://dev.to/tretyakovd/id-token-validation-in-dotnet-2a6e</link>
      <guid>https://dev.to/tretyakovd/id-token-validation-in-dotnet-2a6e</guid>
      <description>&lt;p&gt;Today I've got a challenge.&lt;br&gt;
On one of the projects I'm working on, by business reasons we don't want users to login user 3rd IDP's.&lt;br&gt;
However, we have a requirement to validate user's identity with &lt;a href="https://www.bankid.com/en"&gt;BankID&lt;/a&gt; via &lt;a href="https://www.criipto.com/"&gt;criipto.com&lt;/a&gt;&lt;br&gt;
which provides integration via OpenID.&lt;/p&gt;

&lt;p&gt;What means that user should authenticate with our authority registered in &lt;a href="https://www.criipto.com/"&gt;criipto.com&lt;/a&gt;&lt;br&gt;
and pass issued ID-Token to the backend as a proof of identity.&lt;br&gt;
There are many tools that adds OpenID support to dotnet authentication pipeline,&lt;br&gt;
however it took me a while to find tools to specifically validate ID-Token.&lt;/p&gt;

&lt;p&gt;If that's what u happened to need, below will be an example, with some explanations after.&lt;/p&gt;
&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;You'll need &lt;a href="https://www.nuget.org/packages/IdentityModel.OidcClient.IdentityTokenValidator"&gt;IdentityModel.OidcClient.IdentityTokenValidator&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IdentityModel.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IdentityModel.OidcClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;IdTokenValidation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidateTokenExample&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Obviously constant values must be replaced for test to pass&lt;/span&gt;

    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AUTHORITY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CLIENT-ID"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;IdToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ID-TOKEN"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// GetDiscoveryDocumentAsync extension comes from IdentityModel.OidcClient namespace&lt;/span&gt;
        &lt;span class="c1"&gt;// from IdentityModel.OidcClient package.&lt;/span&gt;
        &lt;span class="n"&gt;DiscoveryDocumentResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetDiscoveryDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;False&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;IsError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JwtHandlerIdentityTokenValidator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OidcClientOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ProviderInformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProviderInformation&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;IssuerName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;KeySet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KeySet&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;IdentityTokenValidationResult&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IdToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;False&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just a small note here: normally you would cache discovery documents, here it's not done for the sake of simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;Even tho &lt;code&gt;ValidateAsync&lt;/code&gt; method has async signature,&lt;br&gt;
I was assuming giving it an authority in &lt;code&gt;OidcClientOptions&lt;/code&gt; would be enough.&lt;br&gt;
I assumed it will fetch discovery document and will take a key from there.&lt;br&gt;
Unfortunately, despite async signature it doesn't do so.&lt;br&gt;
It performs synchronous validation based on data passed in,&lt;br&gt;
therefore you need to fetch document yourself like and pass KeySet in to provider information,&lt;br&gt;
like on the example above.&lt;/p&gt;
&lt;h3&gt;
  
  
  Endpoint is on a different host than authority
&lt;/h3&gt;

&lt;p&gt;By default &lt;code&gt;GetDiscoveryDocumentAsync&lt;/code&gt; doesn't allow discovery document segments from domains that are not matching authority.&lt;br&gt;
For example in case of google (Authority: &lt;a href="https://accounts.google.com/"&gt;https://accounts.google.com/&lt;/a&gt;)&lt;br&gt;
opening discovery document root &lt;a href="https://accounts.google.com/.well-known/openid-configuration"&gt;https://accounts.google.com/.well-known/openid-configuration&lt;/a&gt; you may see that some endpoints&lt;br&gt;
have different domains. Like &lt;code&gt;token_endpoint&lt;/code&gt; will be at &lt;a href="https://oauth2.googleapis.com/token"&gt;https://oauth2.googleapis.com/token&lt;/a&gt; and &lt;code&gt;jwks_uri&lt;/code&gt; will be at &lt;a href="https://www.googleapis.com/oauth2/v3/certs"&gt;https://www.googleapis.com/oauth2/v3/certs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, there is an overload for &lt;code&gt;GetDiscoveryDocumentAsync&lt;/code&gt; where you can specify validation policy,&lt;br&gt;
and either whitelist allowed domains either disable endpoint validation at all, like on the examples below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IdentityModel.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;IdTokenValidation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchGoogleDocumentExample&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;UsingWhitelist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DiscoveryDocumentRequest&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://accounts.google.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AdditionalEndpointBaseAddresses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"https://oauth2.googleapis.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"https://www.googleapis.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"https://openidconnect.googleapis.com/"&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;DiscoveryDocumentResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetDiscoveryDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;False&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;IsError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;WithEndpointValidationDisabled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DiscoveryDocumentRequest&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://accounts.google.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="n"&gt;ValidateEndpoints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;DiscoveryDocumentResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDiscoveryDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;False&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="n"&gt;IsError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case of issues, you might want check example repository at &lt;a href="https://github.com/tretyakov-d/playground-id-token-validation"&gt;https://github.com/tretyakov-d/playground-id-token-validation&lt;/a&gt;.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;I hope it helped&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>openid</category>
    </item>
    <item>
      <title>A cleaner way for configuring DI container in dotnet with ServiceAnnotations</title>
      <dc:creator>Dennis Tretyakov</dc:creator>
      <pubDate>Thu, 28 Jan 2021 18:56:45 +0000</pubDate>
      <link>https://dev.to/tretyakovd/a-cleaner-way-for-configuring-di-container-in-dotnet-with-serviceannotations-2poc</link>
      <guid>https://dev.to/tretyakovd/a-cleaner-way-for-configuring-di-container-in-dotnet-with-serviceannotations-2poc</guid>
      <description>&lt;p&gt;DI is great, but managing the registry of dependencies might be not.&lt;br&gt;
With project growing it becomes harder and harder to maintain DI registry.&lt;/p&gt;

&lt;p&gt;Maintaining the registry manually is not just a pain in the ass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it's a continuous time waste&lt;/li&gt;
&lt;li&gt;it's a popular source of bugs&lt;/li&gt;
&lt;li&gt;it's a great &lt;em&gt;demotivator&lt;/em&gt; to create new small classes, but use the "I'll just stick it here" approach instead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The module approach -- in any form, whether it's framework-specific modules like Autofac.Module,&lt;br&gt;
either using static methods like Microsoft's extension approach (eg. AddLogging, AddMvc),&lt;br&gt;
doesn't solve the problem.&lt;br&gt;
It's an attempt to hide the problem, by giving an illusionary structure, at the same time all the problems remain,&lt;br&gt;
In addition to that we are getting logical conflicts with shared components.&lt;br&gt;
In the end, the only benefit of "modularity"&lt;br&gt;
-- is that it helps to build a bigger heap of code that requires continuous manual care.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't get me wrong here. There is nothing wrong with the modular approach for distributing cross-cutting components,&lt;br&gt;
like the same logging or MVC mentioned above.&lt;br&gt;
I'm talking specifically about services within a single application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is another popular approach -- &lt;em&gt;"conventional"&lt;/em&gt;.&lt;br&gt;
In case you are not familiar with it -- it means scanning an assembly using reflection&lt;br&gt;
and programmatically registering services based on naming conventions.&lt;/p&gt;

&lt;p&gt;That solves the manual part - that's true, however:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;it's not flexible enough. &lt;br&gt;&lt;br&gt;
Yes, typically it does cover, a significant percent of registrations,&lt;br&gt;
but it leaves out lots of details, and as know "the devil hides in details"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the enforced naming pattern harms semantics, or will if it doesn't seem like that in the beginning&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the convention's still something that must be continuously  manually taken care of&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Service Annotations
&lt;/h2&gt;

&lt;p&gt;This is an approach I've come up with several years ago and it has proven itself on a variety of projects.&lt;br&gt;
Finally, I’ve packed it and published it on &lt;a href="https://github.com/tretyakov-d/service-annotations"&gt;Github&lt;/a&gt; and &lt;a href="https://www.nuget.org/packages/ServiceAnnotations/"&gt;NuGet&lt;/a&gt;.&lt;br&gt;
It has quite a minimalistic API, but sufficient to solve all the problems mentioned above.&lt;br&gt;
It is designed for &lt;code&gt;IServiceCollection&lt;/code&gt; so every modern IoC framework supports it.&lt;/p&gt;

&lt;p&gt;It is based on assembly scanning but uses attributes not naming conventions and code to handle special cases.&lt;/p&gt;
&lt;h3&gt;
  
  
  Service Attribute
&lt;/h3&gt;

&lt;p&gt;In a typical project, the attribute handles most of the registrations.&lt;br&gt;
On a class level, you define how it should be registered.&lt;br&gt;
It is required to specify a lifetime.&lt;br&gt;
And optionally you can specify as what type the class should be registered, defaults to itself if not specified.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;examples of Service attribute usage:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transient&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMyService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// will be registered with Transient lifetime&lt;/span&gt;
&lt;span class="c1"&gt;// will be registered as MyService&lt;/span&gt;
&lt;span class="c1"&gt;// equivalent to: services.AddTransient&amp;lt;MyService&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Singleton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMySerivce&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMyService&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// will be registered with Singleton lifetime&lt;/span&gt;
&lt;span class="c1"&gt;// will be registered as IMyService&lt;/span&gt;
&lt;span class="c1"&gt;// equivalent to: services.AddSingleton&amp;lt;IMySerivce, MyService&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scoped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMySerivce&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMyService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// will be registered with Scoped lifetime&lt;/span&gt;
&lt;span class="c1"&gt;// will be registered as MyService and as IMyService&lt;/span&gt;
&lt;span class="c1"&gt;// equivalent to: services.AddScoped&amp;lt;MyService&amp;gt;()&lt;/span&gt;
&lt;span class="c1"&gt;//    .AddScoped&amp;lt;IMySerivce&amp;gt;(serviceProvider =&amp;gt; serviceProvider.GetService&amp;lt;MyService&amp;gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ConfigureServices Attribute
&lt;/h3&gt;

&lt;p&gt;So far we have seen the static part, but there is always a set of services that require custom resolvers,&lt;br&gt;
or even access to configuration or some context data.&lt;br&gt;
Therefore there is a ConfigureServices attribute.&lt;br&gt;
The attributes invoke a specified static method&lt;br&gt;
passing &lt;code&gt;IServiceCollection&lt;/code&gt; instance to the method as a parameter.&lt;br&gt;
&lt;em&gt;Looks for a method named "ConfigureServices" if not other name is specified.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;an example of ConfigureServices attribute usage:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceLifetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transient&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RegisterHttpClient&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_httpContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_httpClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RegisterHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;serviceCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;serviceCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MyService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;httpClient&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"myServiceEndpoint"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// the Service attribute&lt;/span&gt;
&lt;span class="c1"&gt;// will be register service with Transient lifetime&lt;/span&gt;
&lt;span class="c1"&gt;// will be register service as MyService&lt;/span&gt;

&lt;span class="c1"&gt;// the ConfigureService attribute&lt;/span&gt;
&lt;span class="c1"&gt;// will invoke RegisterHttpClient method&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ConfigureServices attribute could as well be used without Service attribute.&lt;br&gt;
It can be applied to any class, the only requirements are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;that method (referred by that attribute) must be static and without overloads.&lt;/li&gt;
&lt;li&gt;objects specified as parameters to be passed to a scanning context. (see Setup and configuration right below)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setup and configuration
&lt;/h2&gt;

&lt;p&gt;First, install NuGet package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ServiceAnnotations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Importing ServiceAnnotations namespace will add an extensions method &lt;code&gt;AddAnnotatedServices&lt;/code&gt; to a &lt;code&gt;IServiceCollection&lt;/code&gt; interface. Which has two parameters, both are optional.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;An assembly to scan. Defaults to &lt;em&gt;calling&lt;/em&gt; assembly, so typically don't need to be specified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Action to configure scan context,&lt;br&gt;
where we can pass objects that would be available&lt;br&gt;
as parameters for ConfigureServices attribute referred methods.&lt;br&gt;
Like in the example above where were using &lt;code&gt;IConfiguration&lt;/code&gt; as a parameter to get a &lt;code&gt;BaseAddress&lt;/code&gt; for an &lt;code&gt;HttpClient&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
You don't need to explicitly add &lt;code&gt;IServiceCollection&lt;/code&gt;, it will be available by default&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ServiceAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAnnotatedServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;_configuration&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ps
&lt;/h2&gt;

&lt;p&gt;I hope it will help you to keep your code cleaners.&lt;br&gt;&lt;br&gt;
Don't hesitate to open issues and contribute &lt;a href="https://github.com/tretyakov-d/service-annotations"&gt;on Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Setting up MSDeploy for CI/CD deployments to IIS</title>
      <dc:creator>Dennis Tretyakov</dc:creator>
      <pubDate>Mon, 18 Jan 2021 21:18:44 +0000</pubDate>
      <link>https://dev.to/tretyakovd/setting-up-msdeploy-for-ci-cd-deployments-to-iis-3m7c</link>
      <guid>https://dev.to/tretyakovd/setting-up-msdeploy-for-ci-cd-deployments-to-iis-3m7c</guid>
      <description>&lt;p&gt;Just a few months ago I was 100% sure I’ll never hear about Windows Server ever again, but not everyone is on Kubernetes or even dotnet core yet, so if you are as lucky as me, and have to setup deployment to the IIS server in 3rd decade of 21st century, here are the detailed instructions how to do it.&lt;/p&gt;

&lt;p&gt;First Let me first clarify something. There is a product name called Web Deploy which provides 2 main components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Command-line tool MSDeploy.exe what we use, to make a deployment to an IIS server.&lt;/li&gt;
&lt;li&gt;A service that must be installed on a server running IIS, where we want to make deployments to.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Just a few tips in advance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You will need a user account with Admin privileges to perform deployment&lt;/li&gt;
&lt;li&gt;You will need to install IIS Management Service
&lt;/li&gt;
&lt;li&gt;You will need to install Microsoft Web Deploy to the server
&lt;/li&gt;
&lt;li&gt;You will need &lt;strong&gt;NO restarts&lt;/strong&gt;, quite a surprise for Windows 😁&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install IIS Management Service
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run (Win + R) -&amp;gt; appwiz.cpl&lt;/li&gt;
&lt;li&gt;Click "Turn Windows features on or off"&lt;/li&gt;
&lt;li&gt;On "Add Roles and Features Wizard" proceed to "Server Roles" (on non-server versions of windows you'll be taken to the same screen immediately)&lt;/li&gt;
&lt;li&gt;Select "Management service"&lt;/li&gt;
&lt;li&gt;Proceed to confirmation and press install (appears instead of Next button)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#install-iis-management-service"&gt;Step by step screenshots&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Microsoft Web Deploy
&lt;/h2&gt;

&lt;p&gt;This is a tool that should be installed on both the server (the machine running IIS) and the client (the machine making a deployment).&lt;/p&gt;

&lt;p&gt;Getting it is is a bit tricky though. There are two actual versions of web deploy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The latest version Microsoft offers to download via Download Center is Web Deploy 3.6 &lt;em&gt;(which one is obsolete)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visual Studio 2017+ comes with Web Deploy 4&lt;br&gt;
As it doesn't make sense to install Visual Studio on production servers, as I found on the &lt;a href="https://developercommunity.visualstudio.com/content/problem/230372/web-deploy-40-msi-location.html"&gt;MS Developer Community forum&lt;/a&gt;&lt;br&gt;
you can download a stand-alone .msi for Web Deploy 4 from &lt;a href="https://download.visualstudio.microsoft.com/download/pr/e1828da1-907a-46fe-a3cf-f3b9ea1c485c/035860f3c0d2bab0458e634685648385/webdeploy_amd64_en-us.msi"&gt;https://download.visualstudio.microsoft.com/download/pr/e1828da1-907a-46fe-a3cf-f3b9ea1c485c/035860f3c0d2bab0458e634685648385/webdeploy_amd64_en-us.msi&lt;/a&gt;&lt;br&gt;
if the link is broken, there is a backup copy on my OneDrive &lt;a href="https://1drv.ms/u/s!Au6taQChFyJmge1TOscBDl0V2FMsJQ"&gt;https://1drv.ms/u/s!Au6taQChFyJmge1TOscBDl0V2FMsJQ&lt;/a&gt;&lt;br&gt;
c&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The versions are not compatible. In other words, you cannot use Web Deploy 4 to deploy to the server with Web Deploy 3.6&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yet another important detail is that the "Typical" installation of MS Deploy doesn't include the server components.&lt;br&gt;
Therefore installing MS Deploy choose either Complete install either Custom and ensure all the components will be installed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#install-microsoft-web-deploy"&gt;Step by step screenshots&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to verify
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Windows services should be up and running
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open Services Manager. Run (Win + R) services.msc&lt;/li&gt;
&lt;li&gt;Locate "Web Deployment Agent Service" and ensure it's started. (this is MSDeploy)&lt;/li&gt;
&lt;li&gt;Locate "Web Management Service" and ensure it's started.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#windows-services-should-be-up-and-running"&gt;Step by step screenshots&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Management service should be present in IIS configuration.
&lt;/h4&gt;

&lt;p&gt;You might also want to adjust some settings&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open IIS Manager. Run (Win + R) inetmgr&lt;/li&gt;
&lt;li&gt;Locate "Management Service" in the Management section of the Server Features view&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to change the settings you'll need to stop the service using the stop button on the right panel.&lt;br&gt;
The interface might seem a bit confusing, but feel free to do so, it stops only the management service itself, not the IIS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#management-service-should-be-present-in-iis-configuration"&gt;Step by step screenshots&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The port should be open
&lt;/h4&gt;

&lt;p&gt;As you could see on Management Service screen in IIS settings, by default it listens to port 8172.&lt;br&gt;
The installer also opens the port in Firewall settings for all IP addresses.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Windows Defender Firewall with Advanced Security. Run (Win + R) wf.msc&lt;/li&gt;
&lt;li&gt;Locate "Web Management Service (HTTP Traffic-In)" rule&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#the-port-should-be-open"&gt;Step by step screenshots&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pack and deploy legacy asp.net app to IIS
&lt;/h2&gt;

&lt;p&gt;That deploy part is way easier or at least doesn't require clicks.&lt;/p&gt;

&lt;h4&gt;
  
  
  To make a package with msbuild
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$proejctPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.\MyProject.csproj'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;msbuild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$projectPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:OutDir&lt;/span&gt;&lt;span class="o"&gt;=.&lt;/span&gt;&lt;span class="n"&gt;\dist\&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:Configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:GenerateSerializationAssemblies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:DeployOnBuild&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:WebPublishMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:PackageAsSingleFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:IncludeSetAclProviderOnDestination&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;/p:AutoParameterizationWebConfigConnectionStrings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  To deploy using msdeploy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.\dist\_PublishedWebsites\MyProject_Package\MyProject.zip'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$serverIpOrHostname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$msDeployPort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'8172'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;${serverIpOrHostname}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;${msDeployPort}&lt;/span&gt;&lt;span class="s2"&gt;/MSDeploy.axd"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$iisWebsiteName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'My Website'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# with -whatIf flag provided, msdeploy just shows what's gonna happen without&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# remove the line with -whatIf flag to make real deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;msdeploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nt"&gt;-whatIf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nt"&gt;-verb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nt"&gt;-allowUntrusted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nt"&gt;-dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ComputerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$endpoint&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="s2"&gt;,UserName=&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="s2"&gt;,Password=&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="s2"&gt;,IncludeAcls=False,AuthType=Basic &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;
     -setParam:name=&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="s2"&gt;IIS Web Application Name&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="s2"&gt;,value=&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="nv"&gt;$iisWebsiteName&lt;/span&gt;&lt;span class="se"&gt;`"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;
     -disableLink:AppPoolExtension &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;
     -disableLink:ContentExtension &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;
     -disableLink:CertificateExtension
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  where is msdeploy.exe
&lt;/h4&gt;

&lt;p&gt;Typically it's: &lt;em&gt;C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe&lt;/em&gt; and yet, no matter how rediculous it is, the Web Deploy 4 is going to be in the same directory :)&lt;/p&gt;

&lt;h4&gt;
  
  
  where is msbuild.exe
&lt;/h4&gt;

&lt;p&gt;With visual studio installed it should be in &lt;em&gt;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe&lt;/em&gt;&lt;br&gt;
Obviously the version number and edition might differ.&lt;/p&gt;

&lt;p&gt;However you don't have to have to install Visual Studio, to have MSBuild on a build agent.&lt;br&gt;
You can download and install &lt;a href="https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&amp;amp;rel=16"&gt;Build Tools for Visual Studio 2019&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#install-iis-management-service"&gt;https://dennistretyakov.com/setting-up-msdeploy-for-ci-cd-deployments-to-iis#install-iis-management-service&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
    </item>
    <item>
      <title>One instance for various service types in Microsoft Extensions DI</title>
      <dc:creator>Dennis Tretyakov</dc:creator>
      <pubDate>Mon, 13 Jul 2020 04:22:20 +0000</pubDate>
      <link>https://dev.to/tretyakovd/one-instance-for-various-service-types-in-microsoft-extensions-di-3a55</link>
      <guid>https://dev.to/tretyakovd/one-instance-for-various-service-types-in-microsoft-extensions-di-3a55</guid>
      <description>&lt;p&gt;Recently moving away from Autofac to Microsoft Extensions DI I've faced a problem&lt;br&gt;
that there is no API to register one service implementation instance (singleton, scoped) for various service types.&lt;/p&gt;
&lt;h2&gt;
  
  
  Autofac behaviour
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Autofac_ReturnsSameInstance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contaienrBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ContainerBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;contaienrBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegisterType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsSelf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsImplementedInterfaces&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SingleInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contaienrBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byInterface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byExactType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resolve&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Same&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byExactType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Microsoft DI behaviour
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DI_ReturnsDifferentInstances&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byInterface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byExactType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byExactType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Achieving similar behaviour on Microsoft DI
&lt;/h2&gt;

&lt;p&gt;Autofac allows multiple keys for the same implementation.&lt;br&gt;
Microsoft DI is one to one map.&lt;br&gt;
To achieve the same behaviour choose the type you will use as the primary key (&lt;code&gt;Service&lt;/code&gt; in the example below).&lt;br&gt;
For all other types (like &lt;code&gt;IService&lt;/code&gt; in the example below) register resolvers that would resolve a service using primary key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;DI_ReturnsSameInstace_LikeAutofac&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byInterface&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byExactType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Same&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byExactType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/tretyakov-d/playground-extensions-di-vs-autofac/blob/master/ExtensionsDIvsAutofac/OneServiceImplementatinoForVaiousTypes.cs"&gt;Code on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
    </item>
    <item>
      <title>My long and rocky road to TDD</title>
      <dc:creator>Dennis Tretyakov</dc:creator>
      <pubDate>Wed, 27 May 2020 07:18:59 +0000</pubDate>
      <link>https://dev.to/tretyakovd/my-long-and-rocky-road-to-tdd-1beb</link>
      <guid>https://dev.to/tretyakovd/my-long-and-rocky-road-to-tdd-1beb</guid>
      <description>&lt;p&gt;Internet is full of odes to TDD&lt;br&gt;
competing over a longer list of benefits and providing basic examples, like how to test a Sum function of a Calc class.&lt;/p&gt;

&lt;p&gt;Despite all of that the entry barrier for TDD is very high, at least for strongly typed languages like C#&lt;br&gt;
where the compiler and good code-completion tools can provide a certain level of illusionary confidence of working software.&lt;/p&gt;

&lt;p&gt;I switched to TDD about six years ago.&lt;br&gt;
And here are the biggest struggles I've had,&lt;br&gt;
when I just started&lt;br&gt;
and I've had them for quite a long time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The upside down
&lt;/h2&gt;

&lt;p&gt;One of the most advertised perks of TDD is "better interfaces"&lt;br&gt;
that is achieved by "first you use, then implement" approach.&lt;br&gt;
Well, at least for me, it didn't work out as easy as it sounds.&lt;/p&gt;

&lt;p&gt;The way I used to work before TDD, was to start each feature with deep and detailed analysis.&lt;br&gt;
I'd try to compile a "big picture" in my head, typically with the help of a pen and paper.&lt;br&gt;
I'd try to plan which components I'd reuse and which ones I'd need to create.&lt;/p&gt;

&lt;p&gt;In the beginning, I haven't seen the contradiction with "first you use, then implement" in that,&lt;br&gt;
so I'd start each new feature following the "big picture" approach,&lt;br&gt;
which typically would be followed by one of two scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I'd start working on lower-level components (like repository) responsibly writing tests first, as the approach requires (pretending that I'm using that).&lt;br&gt;
When the lower level was done, I'd go one level higher and would do the same until I'd reach the top.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I'd start implementing from the top, but still using the model I've planed imagining the "big picture".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither works! There was no effect, it was awkward, clumsy, and slow.&lt;br&gt;
It would cost me lots of time and patience polishing and rewriting the tests again and again until some reasonable level of simplicity and more or less clear semantics were reached,&lt;br&gt;
but even then, in certain cases, changing that wouldn't be an easy task.&lt;/p&gt;

&lt;p&gt;Things changed a lot, when I've tried to give up the "big picture" approach an go the "dumb" way.&lt;/p&gt;

&lt;p&gt;By that I mean — starting with the entry point of a use case and not stepping out of it,&lt;br&gt;
defining dependency interfaces the way I need them here and now to make my test easy first.&lt;br&gt;
And only after the test is done,&lt;br&gt;
I'd go to implement the dependencies or eventually replace the exact matches with the existing ones.&lt;/p&gt;

&lt;p&gt;Obviously the "big picture" part is still necessary,&lt;br&gt;
but it should take place only after the feature is implemented in the simplest way possible!&lt;br&gt;
Only then it's a time to revise the components and refactor the code to extract code duplications in separate components,&lt;br&gt;
apply certain optimizations and etc.&lt;/p&gt;

&lt;p&gt;So the proper name for the approach I called "dumb" should be an "iterative" or simply "agile".&lt;br&gt;
This approach actually embraces the natural evolution of code and leads to TDD promised "better interfaces" and some other practical perks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As there is no need to change existing code, the features are being implemented faster and safer.&lt;/li&gt;
&lt;li&gt;The post-factum emerged design solutions are better in a way as they put emphasis on use cases and their specifics,
whereas thought ahead design emphasizes the common part and hides specifics.&lt;/li&gt;
&lt;li&gt;Since the revision/refactoring can be done separately, apart from the KISS version, the efforts can be measured separately as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  That's not cool
&lt;/h2&gt;

&lt;p&gt;Technically the approach described above killed all the fun, or at least that's how I felt at the beginning.&lt;/p&gt;

&lt;p&gt;Before switching to an "iterative" process (the one mentioned above), which was kinda forced by TDD,&lt;br&gt;
one of the coolest parts of development was planning ahead.&lt;br&gt;
Having lots of discussions with the dev team and business about long and short term plans,&lt;br&gt;
especially with new projects (so-called green fields),&lt;br&gt;
putting together all the knowledge, all speculations, all previous experience to design a perfect foundation... this time.&lt;/p&gt;

&lt;p&gt;That felt like an extremely important and valuable thing to do.&lt;br&gt;
The only thing that might have felt even better was occasionally stating:&lt;br&gt;
"ah, that's already done!", "that will work out of the box", "that won't take much time, we thought it through back then (might be even years ago)"&lt;br&gt;
while discussing new features and obviously getting credit for that.&lt;/p&gt;

&lt;p&gt;Well, all of that was gone! No thinking ahead, no perfect solutions, no more feeling of self-importance — not cool at all.&lt;br&gt;
Even more, sometimes it felt extremely "hacky", like knowing that I could do more, care more, but I didn't... It felt like something very unprofessional.&lt;/p&gt;

&lt;p&gt;On top of that,&lt;br&gt;
as planing ahead was gone, my very deep language knowledge (C# at that moment) had become almost useless, since there was no area to apply it.&lt;/p&gt;

&lt;p&gt;In a while the pragmatism started paying back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Refactoring test-driven code is way "easier" or in other words — faster and safer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In comparison with speculative planning,&lt;br&gt;
revising existing architecture allows to define exact and very granular goals with quite precise estimates.&lt;br&gt;
This is also the point where deep language and framework knowledge kicks in, but in this case — to solve real problems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In addition to more accurate estimates and metrics, as there are less ups and downs, the estimates become also more predictable for business&lt;br&gt;
which adds more trust in the relationship between business and developers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, with TDD I've started spending much more time on application architecture than ever before&lt;br&gt;
without having any doubts from the business side.&lt;/p&gt;

&lt;p&gt;The personal doubts went away as well, as solving real problems in a short time feels and is&lt;br&gt;
way more professional than wasting time on solving speculated problems no matter how realistic they might seem at the moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Am I doing it right?
&lt;/h2&gt;

&lt;p&gt;The deeper I was going to TDD the more granular my code was becoming.&lt;br&gt;
More classes, smaller classes, tons of classes with one public function.&lt;br&gt;
All test-driven, therefore well tested, BUT will it work?&lt;br&gt;
Yet again and again I didn't have confidence in that and had to retest it manually.&lt;br&gt;
That was also adding more to the doubt pile about the value of so much time invested in tests.&lt;/p&gt;

&lt;p&gt;Until one day it hit me! I don't need to do it manually.&lt;br&gt;
For whatever I doubt, whatever I have to retest — I should write a test.&lt;br&gt;
The fun part — sometimes it might be quite challenging to achieve.&lt;/p&gt;

&lt;p&gt;For example, with .net core MVC application, having doubts if the app will start successfully I can write a test for it.&lt;br&gt;
I might have forgotten to register one of the pipeline components for a feature I was working on — I can write a test to check that all controllers can be resolved.&lt;/p&gt;

&lt;p&gt;The same applies to work with framework and other 3rd party components.&lt;br&gt;
Whenever there are doubts about how exactly it behaves or there is uncertainty about behavior in future versions — just test it.&lt;/p&gt;

&lt;p&gt;Some may argue these are not unit tests and this is not TDD.&lt;br&gt;
I'm not going to debate the semantics.&lt;br&gt;
The fact is — it works and it's efficient.&lt;br&gt;
It allows to move on, being confident that the code is working.&lt;br&gt;
The TDD doesn't prevent you from automating manual tests.&lt;br&gt;
The one thing remains though, for TDD to go on, the tests should be fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;There is no perfect design to be applied to any project.&lt;br&gt;
The TDD embraces SOLID principles and allows refactoring on cheap.&lt;br&gt;
Continuous refactoring is the way to keep the project architecture clean, to embrace the use cases and specifics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thinking ahead gives a very nice feeling of importance, but it comes with a huge price.&lt;br&gt;
It makes testing and maintenance complicated (i.e. costly)&lt;br&gt;
as well as emphasizes obviously common things and hides the import specifics of an application use cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The main purpose of testing is confidence in working software.&lt;br&gt;
Whatever is doubted should be tested.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tdd</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Would you ever go back to Angular from React?</title>
      <dc:creator>Dennis Tretyakov</dc:creator>
      <pubDate>Sun, 26 Apr 2020 23:56:28 +0000</pubDate>
      <link>https://dev.to/tretyakovd/would-you-ever-go-back-to-angular-from-react-57ok</link>
      <guid>https://dev.to/tretyakovd/would-you-ever-go-back-to-angular-from-react-57ok</guid>
      <description>&lt;p&gt;I'm not trying to start a holy war!&lt;br&gt;
I can remember a few years ago while having only theoretical knowledge on React, I had lots of discussions about the pros and cons of React vs Angular2.&lt;br&gt;
Now after using React for about 2 years, I can't imagine any reason ever to use Anguar2. Unfortunately, occasionally I have to and it's a pain.&lt;/p&gt;

&lt;p&gt;So, what I'm trying to find out, did I've got too obsessed with React? or it's really that much superior over Angular (and I assume others too, except preact)&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>react</category>
      <category>angular</category>
    </item>
  </channel>
</rss>
