-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbuilding-real-time-apps-with-signalr-in-dotnet.html
97 lines (86 loc) · 48.4 KB
/
building-real-time-apps-with-signalr-in-dotnet.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<!DOCTYPE html><html lang=en prefix="og: https://ogp.me/ns#"><head><meta charset=utf-8><base href=https://blog.georgekosmidis.net/building-real-time-apps-with-signalr-in-dotnet.html><meta name=viewport content="width=device-width, initial-scale=1"><meta name=description content="SignalR is an open-source library that simplifies the process of adding real-time web functionality to applications. It enables two-way communication between the server and the client in real-time. As a part of the .NET ecosystem, SignalR seamlessly integrates with existing .NET applications, offering a robust solution for developing real-time features."><meta name=author content="George Kosmidis"><meta http-equiv=Content-Security-Policy content="script-src 'nonce-b1ede7a7-cbc7-48f2-8aa8-60723da5d543'"><link rel=me type=text/html href=https://github.com/georgekosmidis><link rel=me type=text/html href="https://www.linkedin.com/in/georgekosmidis/"><link rel=me type=text/html href=https://www.youtube.com/c/GeorgeKosmidis><link rel=me type=text/html href="https://sessionize.com/georgekosmidis/"><link rel=me type=text/html href="https://www.meetup.com/munich-dotnet-meetup/members/202480733/profile/"><link rel=me type=text/html href="https://georgekosmidis.net/"><link rel=icon type=image/jpg href=/media/me_180x180.jpg><link rel=apple-touch-icon type=image/jpg sizes=180x180 href=/media/me_180x180.jpg><meta property=og:title content="Building Real-Time Apps with SignalR in .NET"><meta property=og:type content=article><meta property=og:url content=https://blog.georgekosmidis.net/building-real-time-apps-with-signalr-in-dotnet.html><meta property=og:image content=https://blog.georgekosmidis.net/media/100560-feature.png><meta property="og:description " content="SignalR is an open-source library that simplifies the process of adding real-time web functionality to applications. It enables two-way communication between the server and the client in real-time. As a part of the .NET ecosystem, SignalR seamlessly integrates with existing .NET applications, offering a robust solution for developing real-time features."><meta property=og:article:published_time content="05/10/2023 14:30:00"><meta property=og:article:modified_time content="05/10/2023 14:30:00"><meta property=og:article:expiration_time content="12/31/9999 23:59:59"><meta property=og:article:author content="George Kosmidis"><meta property=og:article:section content=""><meta property=og:article:tag content=" dotnet, SignalR"><link href=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css rel=stylesheet integrity=sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3 crossorigin=anonymous><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/cookieconsent@3.1.1/build/cookieconsent.min.css integrity="sha256-zQ0LblD/Af8vOppw18+2anxsuaz3pWYyVWi+bTvTH8Q=" crossorigin=anonymous><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.4.0/styles/vs2015.min.css integrity="sha256-Pi771++jBrwgeHVYGOa1sjN8idXlrrYSKQVI7+JA54k=" crossorigin=anonymous><title>Building Real-Time Apps with SignalR in .NET</title><style>header{background-image:url(/media/header_bg.jpg);background-size:cover;background-position:100% 100%}header svg{width:25px;fill:#f8f9fa}footer svg{width:25px;fill:black}.me{width:180px;height:180px}.article{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-all;word-break:break-word}.article img{max-width:100%;height:auto}h1,h2,h3,h4,h5,h6{font-weight:400}h2{margin-top:4rem}.right-column-container h2,.index-card-wrapper h2{margin-top:0}.h3,h3{margin-top:3rem;font-size:1.65rem}.h4,h4{font-size:1.4eem}.h5,h5{font-size:1.2eem}.h4,.h5,h4,h5{margin-top:2rem}th,strong,b{font-weight:500}a{text-decoration:none}a:hover{text-decoration:underline}a.btn svg{margin-bottom:3px}.article-google-engine-top,.article-google-engine-left{display:none}@media (min-width:992px){.container{max-width:1200px}header > .container{max-width:768px}.article-google-engine-left{display:block}td{padding:10px}}@media (max-width:991.98px){.container{padding-right:calc(var(--bs-gutter-x) * .3);padding-left:calc(var(--bs-gutter-x) * .3)}.article-google-engine-top{display:block}td{padding:5px 2px 5px 2px}}blockquote{text-rendering:optimizelegibility;font-weight:400;line-height:160%;box-sizing:inherit;background-color:#DEEBFF;outline-color:#171717;color:#171717;word-wrap:break-word;word-break:break-word;border:1px solid #DEEBFF;border-radius:.375rem;margin-top:1rem;padding:1rem;font-size:1rem;transition:height .5s ease-in,opacity .5s ease-in;display:block;position:relative}blockquote p{margin-bottom:0}dfn{border-color:#343a40}</style><script async src="https://www.googletagmanager.com/gtag/js?id=UA-3071108-41" nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543></script><script nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}
gtag('js',new Date());gtag('config','UA-3071108-41');</script></head><body class=bg-light><header class="container-fluid p-3 bg-dark border-bottom"> <div class="container text-white"> <div class=row> <div class="col-sm-12 col-md-3 text-center"> <a href="/"> <img src=/media/me_180x180.jpg class="rounded-circle border border-white border-5 me" alt="George Kosmidis - SignalR is an open-source library that simplifies the process of adding real-time web functionality to applications. It enables two-way communication between the server and the client in real-time. As a part of the .NET ecosystem, SignalR seamlessly integrates with existing .NET applications, offering a robust solution for developing real-time features."> </a> </div> <div class="col-sm-12 col-md-9"> <div class=container> <div class=row> <div class=col> <h1 class="display-5 text-center">George Kosmidis</h1> <small class="d-block text-center mt-3">Microsoft MVP | Speaks of Azure, AI & .NET | Founder of Munich .NET <br> Building tomorrow @<div style=display:none>slalom</div> <img src=/media/slalom_small.png title=slalom alt=slalom></small> </div> </div> <div class="row mt-3"> <div class="col text-center"> <a href=https://github.com/georgekosmidis target=_blank class=btn rel=noopener> <svg role=img class=icon viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg> </a> <a href="https://www.linkedin.com/in/georgekosmidis/" target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>LinkedIn</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"></path></svg> </a> <a href=https://www.youtube.com/c/GeorgeKosmidis target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>YouTube</title><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path></svg> </a> <a href="https://sessionize.com/georgekosmidis/" target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 288 288" xmlns=http://www.w3.org/2000/svg><title>Sessionize</title><g transform="translate(0.000000,288.000000) scale(0.100000,-0.100000)"><path d="M135 2861 c-22 -10 -54 -34 -72 -52 -67 -71 -63 22 -63 -1369 0 -1391 -4 -1298 63 -1369 70 -73 42 -71 715 -71 l602 0 0 40 c0 108 -52 232 -127 301 -92 85 -229 125 -469 139 -119 6 -160 13 -190 28 -63 32 -96 81 -108 161 -6 43 12 87 470 1118 263 590 480 1073 483 1073 3 0 22 -42 41 -92 47 -127 157 -344 228 -453 156 -239 367 -450 607 -608 100 -65 373 -202 478 -238 37 -13 67 -27 67 -30 0 -3 -348 -160 -772 -349 -425 -188 -777 -346 -782 -350 -4 -5 11 -18 35 -30 235 -119 367 -331 394 -633 l7 -77 482 0 c444 0 484 2 521 19 51 23 111 89 125 138 8 27 10 249 8 773 -4 660 -6 743 -22 809 -69 289 -197 522 -396 721 -199 199 -432 327 -721 396 -67 16 -148 18 -819 21 -712 3 -747 2 -785 -16z m2069 -483 c99 -51 198 -181 200 -264 1 -57 -21 -79 -77 -78 -137 3 -328 220 -288 328 19 50 84 55 165 14z"></path></g></svg> </a> <a href="https://www.meetup.com/munich-dotnet-meetup/" target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>Meetup</title><path d="M6.98.555a.518.518 0 0 0-.105.011.53.53 0 1 0 .222 1.04.533.533 0 0 0 .409-.633.531.531 0 0 0-.526-.418zm6.455.638a.984.984 0 0 0-.514.143.99.99 0 1 0 1.02 1.699.99.99 0 0 0 .34-1.36.992.992 0 0 0-.846-.482zm-3.03 2.236a5.029 5.029 0 0 0-4.668 3.248 3.33 3.33 0 0 0-1.46.551 3.374 3.374 0 0 0-.94 4.562 3.634 3.634 0 0 0-.605 4.649 3.603 3.603 0 0 0 2.465 1.597c.018.732.238 1.466.686 2.114a3.9 3.9 0 0 0 5.423.992c.068-.047.12-.106.184-.157.987.881 2.47 1.026 3.607.24a2.91 2.91 0 0 0 1.162-1.69 4.238 4.238 0 0 0 2.584-.739 4.274 4.274 0 0 0 1.19-5.789 2.466 2.466 0 0 0 .433-3.308 2.448 2.448 0 0 0-1.316-.934 4.436 4.436 0 0 0-.776-2.873 4.467 4.467 0 0 0-5.195-1.656 5.106 5.106 0 0 0-2.773-.807zm-5.603.817a.759.759 0 0 0-.423.135.758.758 0 1 0 .863 1.248.757.757 0 0 0 .193-1.055.758.758 0 0 0-.633-.328zm15.994 2.37a.842.842 0 0 0-.47.151.845.845 0 1 0 1.175.215.845.845 0 0 0-.705-.365zm-8.15 1.028c.063 0 .124.005.182.014a.901.901 0 0 1 .45.187c.169.134.273.241.432.393.24.227.414.089.534.02.208-.122.369-.219.984-.208.633.011 1.363.237 1.514 1.317.168 1.199-1.966 4.289-1.817 5.722.106 1.01 1.815.299 1.96 1.22.186 1.198-2.136.753-2.667.493-.832-.408-1.337-1.34-1.12-2.26.16-.688 1.7-3.498 1.757-3.93.059-.44-.177-.476-.324-.484-.19-.01-.34.081-.526.362-.169.255-2.082 4.085-2.248 4.398-.296.56-.67.694-1.044.674-.548-.029-.798-.32-.72-.848.047-.31 1.26-3.049 1.323-3.476.039-.265-.013-.546-.275-.68-.263-.135-.572.07-.664.227-.128.215-1.848 4.706-2.032 5.038-.316.576-.65.76-1.152.784-1.186.056-2.065-.92-1.678-2.116.173-.532 1.316-4.571 1.895-5.599.389-.69 1.468-1.216 2.217-.892.387.167.925.437 1.084.507.366.163.759-.277.913-.412.155-.134.302-.276.49-.357.142-.06.343-.095.532-.094zm10.88 2.057a.468.468 0 0 0-.093.011.467.467 0 0 0-.36.555.47.47 0 0 0 .557.36.47.47 0 0 0 .36-.557.47.47 0 0 0-.464-.37zm-22.518.81a.997.997 0 0 0-.832.434 1 1 0 1 0 1.39-.258 1 1 0 0 0-.558-.176zm21.294 2.094a.635.635 0 0 0-.127.013.627.627 0 0 0-.48.746.628.628 0 0 0 .746.483.628.628 0 0 0 .482-.746.63.63 0 0 0-.621-.496zm-18.24 6.097a.453.453 0 0 0-.092.012.464.464 0 1 0 .195.908.464.464 0 0 0 .356-.553.465.465 0 0 0-.459-.367zm13.675 1.55a1.044 1.044 0 0 0-.583.187 1.047 1.047 0 1 0 1.456.265 1.044 1.044 0 0 0-.873-.451zM11.4 22.154a.643.643 0 0 0-.36.115.646.646 0 0 0-.164.899.646.646 0 0 0 .899.164.646.646 0 0 0 .164-.898.646.646 0 0 0-.54-.28z"></path></svg> </a> </div> </div> </div> </div> </div> </div> </header> <main class="container bg-light pt-4 pb-5"> <div class="row masonry"> <div class=container> <div class="row mx-auto article-google-engine-top"> <div class="col pb-4"> <div class="card text-center bg-transparent border-0" style=height:33px><script async src="https://cse.google.com/cse.js?cx=c67a1214306f44e87" nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543></script><div class=gcse-searchbox-only></div> </div> </div> </div> <div class="row mx-auto"> <div class="col-lg-8 mb-4 shadow bg-white rounded"> <div class="container article"> <div class="row border-end border-start border-bottom bg-light"> <div class=col style=text-align:center> <a class="btn align-middle" href=https://github.com/georgekosmidis/blog.georgekosmidis.net//tree/main/workables/articles/100560-building-real-time-apps-with-signalr-in-dotnet/content.html role=button style=text-decoration:none target=_blank rel=noopener> <svg xmlns=http://www.w3.org/2000/svg width=22 height=22 fill=currentColor viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" /></svg> Edit Page </a> <a class="btn align-middle" href="https://github.com/georgekosmidis/blog.georgekosmidis.net//issues/new?permalink=https://github.com/georgekosmidis/blog.georgekosmidis.net//tree/main/workables/articles/100560-building-real-time-apps-with-signalr-in-dotnet/content.html" role=button style=text-decoration:none target=_blank rel=noopener> <svg xmlns=http://www.w3.org/2000/svg width=22 height=22 fill=currentColor viewBox="0 0 24 24"><path fill-rule=evenodd d="M2.5 12a9.5 9.5 0 1119 0 9.5 9.5 0 01-19 0zM12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1zm0 13a2 2 0 100-4 2 2 0 000 4z"></path></svg> Create Issue </a> <a class="btn align-middle" href="https://github.com/georgekosmidis/blog.georgekosmidis.net/discussions/new?title=Building Real-Time Apps with SignalR in .NET&body=Article%20URL:%20https://blog.georgekosmidis.net//building-real-time-apps-with-signalr-in-dotnet.html" role=button style=text-decoration:none target=_blank rel=noopener> <svg aria-hidden=true height=22 viewBox="0 0 16 16" width=22><path fill-rule=evenodd d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path></svg> Discuss </a> <a class="btn ms-4" href="/" role=button style=text-decoration:none> <svg xmlns=http://www.w3.org/2000/svg width=22 height=22 class=mb-1 fill=currentColor class="bi bi-house-fill" viewBox="0 0 16 16"><path fill-rule=evenodd d="m8 3.293 6 6V13.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5V9.293l6-6zm5-.793V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"></path><path fill-rule=evenodd d="M7.293 1.5a1 1 0 0 1 1.414 0l6.647 6.646a.5.5 0 0 1-.708.708L8 2.207 1.354 8.854a.5.5 0 1 1-.708-.708L7.293 1.5z"></path></svg> Go Home </a> </div> </div> <div class=row> <div class="col mt-3"> <h1 class="text-center display-6">Building Real-Time Apps with SignalR in .NET</h1> </div> </div> <div class=row> <div class="col pb-2"> <div class="text-black-50 text-center"> <small>by <a href="https://georgekosmidis.net/" rel=noopener>George Kosmidis</a> / Published 2 years ago</small> </div> </div> </div> <div class=row> <div class="col text-center"> <img src=/media/100560-feature.png alt="Building Real-Time Apps with SignalR in .NET"> </div> </div> <div class=row> <div class="col pt-3 pb-6"> <h2 id=introduction>Introduction</h2> <p>In the digital age, the demand for interactive, real-time web applications is more prominent than ever. From live chat systems to instant notifications, users expect seamless, immediate interactions when they navigate online platforms. This is where SignalR, a powerful .NET library, steps in to revolutionize how developers build these dynamic experiences.</p> <h2 id=what-is-signalr>What is SignalR?</h2> <p>SignalR is an open-source library that simplifies the process of adding real-time web functionality to applications. It enables two-way communication between the server and the client in real-time. As a part of the .NET ecosystem, SignalR seamlessly integrates with existing .NET applications, offering a robust solution for developing real-time features.</p> <h3 id=key-features-and-benefits>Key Features and Benefits</h3> <ul><li><strong>Real-Time Communication</strong>: SignalR facilitates instant data exchange, enabling features like chat systems, live feeds, and notifications.</li> <li><strong>Scalability</strong>: Easily scales to accommodate a high number of connections, making it suitable for large-scale applications.</li> <li><strong>Fallback Mechanisms</strong>: It intelligently falls back to older technologies if WebSockets are not supported, ensuring wide compatibility.</li> <li><strong>High-Level Abstractions</strong>: SignalR abstracts complex networking and concurrency issues, allowing developers to focus on core functionality.</li></ul> <h2 id=getting-started-with-signalr>Getting Started with SignalR</h2> <h3 id=prerequisites>Prerequisites</h3> <ul><li>Basic knowledge of .NET.</li> <li>Visual Studio or another .NET-compatible IDE.</li> <li>Latest .NET Core SDK installed on your machine.</li></ul> <h3 id=setting-up-a-new.net-project-with-signalr>Setting Up a New .NET Project with SignalR</h3> <ol><li>Create a new .NET Core Web Application.</li> <li>Install the <code>Microsoft.AspNetCore.SignalR</code> NuGet package.</li> <li>Configure SignalR in the <code>Startup.cs</code> file.</li></ol> <h2 id=core-concepts-of-signalr>Core Concepts of SignalR</h2> <h3 id=hubs>Hubs</h3> <p>Hubs in SignalR serve as the main conduit for communication between clients and the server. They are designed to simplify the process of sending messages back and forth, abstracting the complexities of connection management and method invocation. Here's a closer look at what makes Hubs a central piece of the SignalR architecture:</p> <ul><li><strong>Method Invocation</strong>: Hubs allow the server to call methods on clients and vice versa. This means you can easily call JavaScript functions from C# code on the server and C# methods from JavaScript in the client.</li> <li><strong>Strong Typing</strong>: SignalR provides a strongly typed hub interface, enabling developers to define methods on the server that clients can call, leading to compile-time checking and IntelliSense support in IDEs.</li> <li><strong>Group Management</strong>: Hubs facilitate adding or removing connections to named groups, allowing for targeted messaging to subsets of connected clients, such as a chat room or a specific user category.</li></ul> <h3 id=connections>Connections</h3> <p>The concept of connections is fundamental to understanding SignalR. Each client connecting to a SignalR hub establishes a "connection" represented by a unique connection ID. SignalR abstracts the connection details, allowing developers to focus on the application logic rather than the intricacies of real-time communication protocols.</p> <ul><li><strong>Connection Lifecycle</strong>: SignalR manages the lifecycle of connections, handling events like connect, disconnect, and reconnect. Developers can hook into these events to perform actions, such as updating a user list when someone joins or leaves a chat.</li> <li><strong>Transport Fallbacks</strong>: SignalR uses WebSockets as its primary transport but can fall back to other techniques (like server-sent events or long polling) if necessary. This ensures compatibility across a wide range of browsers and network environments.</li> <li><strong>Connection Security</strong>: SignalR integrates with ASP.NET Core's security models, allowing the use of authentication and authorization mechanisms to control access to hubs and methods based on the connected user.</li></ul> <h3 id=messages>Messages</h3> <p>Messages are the payload of information exchanged between clients and servers. SignalR simplifies the process of sending messages, whether it's broadcasting to all connected clients, targeting specific groups, or sending direct messages to individual connections.</p> <ul><li><strong>Broadcasting</strong>: Sending a message to all connected clients simultaneously. This is useful for features like announcements or live updates.</li> <li><strong>User and Group Messages</strong>: SignalR allows messages to be sent to specific groups or users. This is particularly useful for applications like chat rooms, where you might want to send a message to everyone in a particular room or direct messages between users.</li> <li><strong>Client-to-Server and Server-to-Client</strong>: Messages can flow in both directions. Clients can invoke methods on the server, and the server can send messages to one or more clients.</li> <li><strong>Performance Considerations</strong>: While sending messages is straightforward, developers must consider the impact of message size and frequency on performance and scalability, especially in applications with a large number of clients.</li></ul> <h2 id=building-a-real-time-chat-application-with-signalr>Building a Real-Time Chat Application with SignalR</h2> <p>This tutorial will guide you through creating a feature-rich chat application using SignalR, covering both the basics and some advanced functionalities like handling user connections/disconnections and enabling private messaging.</p> <h3 id=server-side-hub-implementation>Server-Side (Hub) Implementation</h3> <p>The following C# code defines a <code>ChatHub</code> class that inherits from SignalR's <code>Hub</code> class, which is a central part of building real-time, interactive applications using SignalR.</p> <pre><code class=language-csharp>public class ChatHub : Hub
{
// Notifies all connected clients when a new user connects to the chat
public override async Task OnConnectedAsync()
{
await Clients.All.SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} joined the chat");
await base.OnConnectedAsync();
}
// Notifies all connected clients when a user disconnects from the chat
public override async Task OnDisconnectedAsync(Exception exception)
{
await Clients.All.SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} left the chat");
await base.OnDisconnectedAsync(exception);
}
// Sends a message to all connected clients from a specific user
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// Sends a private message to a specific client, identified by connection ID
public async Task SendPrivateMessage(string toConnectionId, string message)
{
await Clients.Client(toConnectionId).SendAsync("ReceiveMessage", "Private", message);
}
}
</code></pre> <p>Let's break down what each part of this code does:</p> <ol><li><code>OnConnectedAsync</code> Method</li></ol> <ul><li><strong>Purpose</strong>: Automatically called when a new client successfully establishes a connection with the SignalR server.</li> <li><strong>Functionality</strong>: When a client connects, this method sends a message to all connected clients (including the one just connected) notifying them that someone has joined the chat. It uses <code>Clients.All.SendAsync</code> to broadcast the message to every connected client.</li> <li><strong><code>Context.ConnectionId</code></strong>: A unique identifier for the connected client, used here to indicate which client has joined the chat.</li></ul> <ol start=2><li><code>OnDisconnectedAsync</code> Method</li></ol> <ul><li><strong>Purpose</strong>: Invoked when a client disconnects from the SignalR server, whether due to closing their browser, losing connection, or if the server disconnects them.</li> <li><strong>Functionality</strong>: Similar to <code>OnConnectedAsync</code>, it broadcasts a message to all connected clients that a particular client (identified by <code>Context.ConnectionId</code>) has left the chat.</li> <li><strong>Exception Handling</strong>: The method takes an <code>Exception</code> parameter that can be used to determine if the disconnection was due to an error.</li></ul> <ol start=3><li><code>SendMessage</code> Method</li></ol> <ul><li><strong>Purpose</strong>: Allows a client to send a message to all connected clients.</li> <li><strong>Functionality</strong>: This method is called by a client wishing to broadcast a message. It takes two parameters: <code>user</code> (the name or identifier of the sender) and <code>message</code> (the content to be broadcast). It then uses <code>Clients.All.SendAsync</code> to distribute the message to all clients, including the sender.</li></ul> <ol start=4><li><code>SendPrivateMessage</code> Method</li></ol> <ul><li><strong>Purpose</strong>: Enables sending a private message to a specific client identified by their connection ID.</li> <li><strong>Functionality</strong>: This method allows sending a message to a single client, identified by the <code>toConnectionId</code> parameter. The message is marked as "Private" to distinguish it from public messages. It uses <code>Clients.Client(toConnectionId).SendAsync</code> to send the message only to the specified client.</li></ul> <h3 id=client-side-implementation>Client-Side Implementation</h3> <p>We'll implement a basic UI for displaying messages and a form for sending them, along with connection and disconnection notifications.</p> <h4 id=html-markup>HTML Markup</h4> <pre><code class=language-html><!DOCTYPE html>
<html>
<head>
<title>SignalR Chat</title>
</head>
<body>
<div id="chatWindow"></div>
<input type="text" id="messageInput" placeholder="Enter your message..." />
<button id="sendButton">Send</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.min.js"></script>
<script src="chat.js"></script>
</body>
</html>
</code></pre> <h4 id=javascript-chat.js>JavaScript (chat.js)</h4> <pre><code class=language-javascript>const connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
// Disable the send button until connection is established
document.getElementById("sendButton").disabled = true;
// Display messages in the chat window
connection.on("ReceiveMessage", function(user, message) {
const msg = document.createElement("div");
msg.textContent = `${user}: ${message}`;
document.getElementById("chatWindow").appendChild(msg);
});
// Start the connection
connection.start().then(function() {
document.getElementById("sendButton").disabled = false;
}).catch(function(err) {
return console.error(err.toString());
});
// Send message to the server
document.getElementById("sendButton").addEventListener("click", function(e) {
const message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", "User", message).catch(err => console.error(err.toString()));
e.preventDefault(); // Prevent form from submitting
});
// Optionally, handle the enter key
document.getElementById("messageInput").addEventListener("keypress", function(e) {
if (e.key === "Enter") {
document.getElementById("sendButton").click();
}
});
</code></pre> <p>Skipping the HTML Snippet, here's a step-by-step explanation of what's happening in the code:</p> <ol><li><p><strong>Initialize SignalR Connection</strong>:</p> <ul><li><code>const connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();</code></li> <li>This line creates a new SignalR connection to the server. The <code>.withUrl("/chatHub")</code> part specifies the URL endpoint of the SignalR hub on the server. The <code>.build()</code> method finalizes the connection configuration.</li></ul> </li> <li><p><strong>Disable Send Button Initially</strong>:</p> <ul><li><code>document.getElementById("sendButton").disabled = true;</code></li> <li>Before the connection is established, the send button is disabled to prevent users from trying to send messages.</li></ul> </li> <li><p><strong>Setting Up a Message Receiver</strong>:</p> <ul><li>The <code>connection.on("ReceiveMessage", function(user, message) { ... });</code> block sets up an event listener that waits for messages from the server. Whenever the server sends a message using the "ReceiveMessage" event, this function is triggered. The function creates a new <code>div</code> element, sets its text content to include the user's name and message, and appends this <code>div</code> to the "chatWindow" element. This way, messages received from the server are displayed to the user.</li></ul> </li> <li><p><strong>Establishing the Connection</strong>:</p> <ul><li><code>connection.start().then(function() { ... }).catch(function(err) { ... });</code></li> <li>This attempts to start the connection to the server. If successful, it enables the send button by setting its <code>disabled</code> property to <code>false</code>, allowing users to send messages. If there's an error (for example, if the server is unreachable), it logs the error to the console.</li></ul> </li> <li><p><strong>Sending Messages to the Server</strong>:</p> <ul><li>The event listener for the send button (<code>document.getElementById("sendButton").addEventListener("click", function(e) { ... });</code>) listens for click events. When the button is clicked, it retrieves the message from the "messageInput" input field and sends it to the server by calling <code>connection.invoke("SendMessage", "User", message)</code>. The "SendMessage" part corresponds to a method on the server-side hub that's designed to receive messages from clients. If there's an error in sending the message, it catches and logs the error. The <code>e.preventDefault();</code> call prevents the default form submission behavior, keeping the page from reloading.</li></ul> </li> <li><p><strong>Handling Enter Key Press</strong>:</p> <ul><li>The last part (<code>document.getElementById("messageInput").addEventListener("keypress", function(e) { ... });</code>) adds an event listener to the message input field that listens for keypress events. If the pressed key is "Enter", it programmatically clicks the send button, allowing users to send messages by pressing Enter instead of clicking the button.</li></ul> </li></ol> <h3 id=extended-features-and-considerations>Extended Features and Considerations</h3> <ul><li><strong>User Authentication and Identification</strong>: Integrate ASP.NET Core Identity for meaningful user identification.</li> <li><strong>Message Persistence</strong>: Store messages in a database to maintain chat history.</li> <li><strong>Group Management</strong>: Add functionality for users to create and join chat rooms or groups, with messages sent only to group members.</li></ul> <h2 id=advanced-signalr-features>Advanced SignalR Features</h2> <h3 id=working-with-groups>Working with Groups</h3> <p>Groups in SignalR serve as a means to categorize connections. This feature is invaluable for applications requiring targeted messaging, such as a chat application with private rooms or a notification system where messages are sent to specific subsets of users.</p> <h4 id=how-to-use-groups>How to Use Groups</h4> <ul><li><strong>Creating and Joining Groups</strong>: You can dynamically add connections to groups without needing to define them in advance. This is done server-side, typically in your hub class.</li></ul> <pre><code class=language-csharp>public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}.");
}
</code></pre> <ul><li><strong>Sending Messages to Groups</strong>: Once a connection is added to a group, you can send messages to all connections in that group. This allows for efficient communication with specific user segments.</li></ul> <pre><code class=language-csharp>public async Task SendMessageToGroup(string groupName, string message)
{
await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
}
</code></pre> <h3 id=handling-connections>Handling Connections</h3> <p>Effective connection management is crucial for maintaining the integrity and performance of real-time applications.</p> <h4 id=connection-lifecycle-events>Connection Lifecycle Events</h4> <ul><li><p><strong>OnConnectedAsync</strong>: Override this method in your hub to perform actions when a new connection is established, such as logging or custom authentication.</p> </li> <li><p><strong>OnDisconnectedAsync</strong>: Use this to clean up when a connection is closed, such as removing users from groups or updating UI elements to reflect the disconnection.</p> </li></ul> <pre><code class=language-csharp>public override async Task OnConnectedAsync()
{
// Custom logic here
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
// Cleanup logic here
await base.OnDisconnectedAsync(exception);
}
</code></pre> <blockquote> <p>Implementing connection tracking can be done by maintaining a mapping of user identifiers to connection IDs, enabling targeted communication and connection-specific actions.</p> </blockquote> <h3 id=scaling-signalr-applications>Scaling SignalR Applications</h3> <p>For SignalR applications to support thousands to millions of concurrent connections, scaling out is necessary. Azure SignalR Service is designed to handle this by offloading the connection management and scaling requirements from your servers. Although an intro into Azure SignalR worths an article, here is a small spoiler!</p> <h4 id=how-azure-signalr-service-works>How Azure SignalR Service Works</h4> <ul><li><p><strong>Automatic Scaling</strong>: Azure SignalR Service automatically scales to accommodate the number of connections, allowing you to focus on application logic rather than infrastructure concerns.</p> </li> <li><p><strong>Serverless Compatibility</strong>: It can operate in a serverless mode, enabling you to build applications without managing servers, further simplifying the development process.</p> </li> <li><p><strong>Integration with Azure Functions</strong>: You can integrate SignalR with Azure Functions to trigger real-time messages in response to events, creating highly responsive and dynamic applications.</p> </li></ul> <h4 id=implementing-azure-signalr-service>Implementing Azure SignalR Service</h4> <ol><li><p><strong>Provision Azure SignalR Service</strong>: Through the Azure portal, create an instance of the Azure SignalR Service and configure it according to your application's needs.</p> </li> <li><p><strong>Update Your Application Configuration</strong>: Integrate the service into your application by updating the connection strings and configuring your application to use Azure SignalR Service.</p> </li> <li><p><strong>Adjust Your Code for Azure SignalR</strong>: Ensure your application logic is compatible with the distributed nature of the cloud service, especially regarding connection and group management.</p> </li></ol> <h2 id=conclusion>Conclusion</h2> <p>In conclusion, SignalR stands out as a powerful tool in the .NET arsenal for building sophisticated real-time web applications. Its ability to facilitate instantaneous communication between the server and the client opens up endless possibilities for creating interactive, dynamic user experiences. By leveraging SignalR, we can easily implement features such as live chats, real-time updates, and interactive games, significantly enhancing the user engagement and satisfaction.</p> </div> </div> <div class="row border-end border-start border-top bg-light mt-4"> <div class="col pt-2" style=text-align:center> This page is <strong>open source</strong>. Noticed a typo? Or something unclear?<br> <a class="btn align-middle" href=https://github.com/georgekosmidis/blog.georgekosmidis.net//tree/main/workables/articles/100560-building-real-time-apps-with-signalr-in-dotnet/content.html role=button style=text-decoration:none target=_blank rel=noopener> <svg xmlns=http://www.w3.org/2000/svg width=22 height=22 fill=currentColor viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" /></svg> Edit Page </a> <a class="btn align-middle" href="https://github.com/georgekosmidis/blog.georgekosmidis.net//issues/new?permalink=https://github.com/georgekosmidis/blog.georgekosmidis.net//tree/main/workables/articles/100560-building-real-time-apps-with-signalr-in-dotnet/content.html" role=button style=text-decoration:none target=_blank rel=noopener> <svg xmlns=http://www.w3.org/2000/svg width=22 height=22 fill=currentColor viewBox="0 0 24 24"><path fill-rule=evenodd d="M2.5 12a9.5 9.5 0 1119 0 9.5 9.5 0 01-19 0zM12 1C5.925 1 1 5.925 1 12s4.925 11 11 11 11-4.925 11-11S18.075 1 12 1zm0 13a2 2 0 100-4 2 2 0 000 4z"></path></svg> Create Issue </a> <a class="btn align-middle" href="https://github.com/georgekosmidis/blog.georgekosmidis.net/discussions/new?title=Building Real-Time Apps with SignalR in .NET&body=Article%20URL:%20https://blog.georgekosmidis.net//building-real-time-apps-with-signalr-in-dotnet.html" role=button style=text-decoration:none target=_blank rel=noopener> <svg aria-hidden=true height=22 viewBox="0 0 16 16" version=1.1 width=22 data-view-component=true class="octicon octicon-comment-discussion UnderlineNav-octicon d-none d-sm-inline"><path fill-rule=evenodd d="M1.5 2.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v5.5a.25.25 0 01-.25.25h-3.5a.75.75 0 00-.53.22L3.5 11.44V9.25a.75.75 0 00-.75-.75h-1a.25.25 0 01-.25-.25v-5.5zM1.75 1A1.75 1.75 0 000 2.75v5.5C0 9.216.784 10 1.75 10H2v1.543a1.457 1.457 0 002.487 1.03L7.061 10h3.189A1.75 1.75 0 0012 8.25v-5.5A1.75 1.75 0 0010.25 1h-8.5zM14.5 4.75a.25.25 0 00-.25-.25h-.5a.75.75 0 110-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0114.25 12H14v1.543a1.457 1.457 0 01-2.487 1.03L9.22 12.28a.75.75 0 111.06-1.06l2.22 2.22v-2.19a.75.75 0 01.75-.75h1a.25.25 0 00.25-.25v-5.5z"></path></svg> Discuss </a> </div> </div> </div> </div> <div class=col-lg-4> <div class="container px-0 mx-0 right-column-container"> <div class="col mb-4"> <div class="card text-center bg-transparent border-0" style=height:33px><script async src="https://cse.google.com/cse.js?cx=c67a1214306f44e87" nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543></script><div class=gcse-searchbox-only></div> </div> </div> <div class="col mb-4"> <div class="card shadow"> <img src=/media/mvp.png class="bd-placeholder-img card-img-top" alt="Microsoft MVP - George Kosmidis"> <a href=https://mvp.microsoft.com/en-us/PublicProfile/5004591 target=_blank title="Microsoft MVP - George Kosmidis" class=stretched-link rel=noopener></a> </div> </div> <div class="col mb-4"> <div class="card shadow"> <img src=/media/azure-architecture-icons.png class="bd-placeholder-img card-img-top" alt="Azure Architecture Icons - SVGs, PNGs and draw.io libraries"> <a href=/azure-architecture-icons.html target=_top title="Azure Architecture Icons - SVGs, PNGs and draw.io libraries" class=stretched-link rel=noopener></a> </div> </div> </div> </div> </div> <div class=col-lg-4> </div> </div> </div> </div> </main> <footer class="footer mt-auto py-3 border-top"> <div class="container mx-auto text-center"> <a href=https://github.com/georgekosmidis target=_blank class=btn rel=noopener> <svg role=img class=icon viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg> </a> <a href="https://www.linkedin.com/in/georgekosmidis/" target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>LinkedIn</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"></path></svg> </a> <a href=https://www.youtube.com/c/GeorgeKosmidis target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>YouTube</title><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path></svg> </a> <a href="https://sessionize.com/georgekosmidis/" target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 288 288" xmlns=http://www.w3.org/2000/svg><title>Sessionize</title><g transform="translate(0.000000,288.000000) scale(0.100000,-0.100000)"><path d="M135 2861 c-22 -10 -54 -34 -72 -52 -67 -71 -63 22 -63 -1369 0 -1391 -4 -1298 63 -1369 70 -73 42 -71 715 -71 l602 0 0 40 c0 108 -52 232 -127 301 -92 85 -229 125 -469 139 -119 6 -160 13 -190 28 -63 32 -96 81 -108 161 -6 43 12 87 470 1118 263 590 480 1073 483 1073 3 0 22 -42 41 -92 47 -127 157 -344 228 -453 156 -239 367 -450 607 -608 100 -65 373 -202 478 -238 37 -13 67 -27 67 -30 0 -3 -348 -160 -772 -349 -425 -188 -777 -346 -782 -350 -4 -5 11 -18 35 -30 235 -119 367 -331 394 -633 l7 -77 482 0 c444 0 484 2 521 19 51 23 111 89 125 138 8 27 10 249 8 773 -4 660 -6 743 -22 809 -69 289 -197 522 -396 721 -199 199 -432 327 -721 396 -67 16 -148 18 -819 21 -712 3 -747 2 -785 -16z m2069 -483 c99 -51 198 -181 200 -264 1 -57 -21 -79 -77 -78 -137 3 -328 220 -288 328 19 50 84 55 165 14z"></path></g></svg> </a> <a href=https://www.facebook.com/groups/msdevtech target=_blank class=btn rel=noopener> <svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 400 400" role=img><path d="M353.701,0H55.087C24.665,0,0.002,24.662,0.002,55.085v298.616c0,30.423,24.662,55.085,55.085,55.085 h147.275l0.251-146.078h-37.951c-4.932,0-8.935-3.988-8.954-8.92l-0.182-47.087c-0.019-4.959,3.996-8.989,8.955-8.989h37.882 v-45.498c0-52.8,32.247-81.55,79.348-81.55h38.65c4.945,0,8.955,4.009,8.955,8.955v39.704c0,4.944-4.007,8.952-8.95,8.955 l-23.719,0.011c-25.615,0-30.575,12.172-30.575,30.035v39.389h56.285c5.363,0,9.524,4.683,8.892,10.009l-5.581,47.087 c-0.534,4.506-4.355,7.901-8.892,7.901h-50.453l-0.251,146.078h87.631c30.422,0,55.084-24.662,55.084-55.084V55.085 C408.786,24.662,384.124,0,353.701,0z" /></svg> </a> <a href="https://www.meetup.com/munich-dotnet-meetup/" target=_blank class=btn rel=noopener> <svg role=img viewBox="0 0 24 24" xmlns=http://www.w3.org/2000/svg><title>Meetup</title><path d="M6.98.555a.518.518 0 0 0-.105.011.53.53 0 1 0 .222 1.04.533.533 0 0 0 .409-.633.531.531 0 0 0-.526-.418zm6.455.638a.984.984 0 0 0-.514.143.99.99 0 1 0 1.02 1.699.99.99 0 0 0 .34-1.36.992.992 0 0 0-.846-.482zm-3.03 2.236a5.029 5.029 0 0 0-4.668 3.248 3.33 3.33 0 0 0-1.46.551 3.374 3.374 0 0 0-.94 4.562 3.634 3.634 0 0 0-.605 4.649 3.603 3.603 0 0 0 2.465 1.597c.018.732.238 1.466.686 2.114a3.9 3.9 0 0 0 5.423.992c.068-.047.12-.106.184-.157.987.881 2.47 1.026 3.607.24a2.91 2.91 0 0 0 1.162-1.69 4.238 4.238 0 0 0 2.584-.739 4.274 4.274 0 0 0 1.19-5.789 2.466 2.466 0 0 0 .433-3.308 2.448 2.448 0 0 0-1.316-.934 4.436 4.436 0 0 0-.776-2.873 4.467 4.467 0 0 0-5.195-1.656 5.106 5.106 0 0 0-2.773-.807zm-5.603.817a.759.759 0 0 0-.423.135.758.758 0 1 0 .863 1.248.757.757 0 0 0 .193-1.055.758.758 0 0 0-.633-.328zm15.994 2.37a.842.842 0 0 0-.47.151.845.845 0 1 0 1.175.215.845.845 0 0 0-.705-.365zm-8.15 1.028c.063 0 .124.005.182.014a.901.901 0 0 1 .45.187c.169.134.273.241.432.393.24.227.414.089.534.02.208-.122.369-.219.984-.208.633.011 1.363.237 1.514 1.317.168 1.199-1.966 4.289-1.817 5.722.106 1.01 1.815.299 1.96 1.22.186 1.198-2.136.753-2.667.493-.832-.408-1.337-1.34-1.12-2.26.16-.688 1.7-3.498 1.757-3.93.059-.44-.177-.476-.324-.484-.19-.01-.34.081-.526.362-.169.255-2.082 4.085-2.248 4.398-.296.56-.67.694-1.044.674-.548-.029-.798-.32-.72-.848.047-.31 1.26-3.049 1.323-3.476.039-.265-.013-.546-.275-.68-.263-.135-.572.07-.664.227-.128.215-1.848 4.706-2.032 5.038-.316.576-.65.76-1.152.784-1.186.056-2.065-.92-1.678-2.116.173-.532 1.316-4.571 1.895-5.599.389-.69 1.468-1.216 2.217-.892.387.167.925.437 1.084.507.366.163.759-.277.913-.412.155-.134.302-.276.49-.357.142-.06.343-.095.532-.094zm10.88 2.057a.468.468 0 0 0-.093.011.467.467 0 0 0-.36.555.47.47 0 0 0 .557.36.47.47 0 0 0 .36-.557.47.47 0 0 0-.464-.37zm-22.518.81a.997.997 0 0 0-.832.434 1 1 0 1 0 1.39-.258 1 1 0 0 0-.558-.176zm21.294 2.094a.635.635 0 0 0-.127.013.627.627 0 0 0-.48.746.628.628 0 0 0 .746.483.628.628 0 0 0 .482-.746.63.63 0 0 0-.621-.496zm-18.24 6.097a.453.453 0 0 0-.092.012.464.464 0 1 0 .195.908.464.464 0 0 0 .356-.553.465.465 0 0 0-.459-.367zm13.675 1.55a1.044 1.044 0 0 0-.583.187 1.047 1.047 0 1 0 1.456.265 1.044 1.044 0 0 0-.873-.451zM11.4 22.154a.643.643 0 0 0-.36.115.646.646 0 0 0-.164.899.646.646 0 0 0 .899.164.646.646 0 0 0 .164-.898.646.646 0 0 0-.54-.28z"></path></svg> </a> <div class=text-muted>© Copyright 2022, <a href="https://georgekosmidis.net/" target=_blank rel=noopener>George Kosmidis</a> <br> Is lawyer the most boring job in the world? Read the <a href=/privacy.html>Privacy Policy</a> to find out. <br><small class=text-black-50>Last Build: 2025-02-13T03:03:58+00:00 </small> </div> </div> </footer><script src=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js integrity=sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p crossorigin=anonymous nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543></script><script src=https://cdn.jsdelivr.net/npm/cookieconsent@3.1.1/build/cookieconsent.min.js integrity="sha256-5VhCqFam2Cn+yjw61zbBNrbHVJ6SRydPeKopYlngbiQ=" crossorigin=anonymous nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543></script><script src=https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.4.0/highlight.min.js integrity="sha256-GCgWKkl4RE3+M/TNH5d/F80Tz30PQT+Oubq5Q3I5c20=" crossorigin=anonymous nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543></script><script nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543>hljs.highlightAll();</script><script nonce=b1ede7a7-cbc7-48f2-8aa8-60723da5d543>window.cookieconsent.initialise({"palette":{"popup":{"background":"#343c66","text":"#cfcfe8"},"button":{"background":"#f71559"}},"showLink":false,"theme":"classic","content":{"message":"Cookie Warning! My blog uses Google Analytics so Google knows you are here. Maybe others too, including FBI, CIA, NASA, ESA, IIS and other acronyms. If you don't like it, you must unfortunately go :(","dismiss":"It's just code, I 'll stay!"}});</script></body></html>