>();
16 | options.Address = new Uri(settings.CurrentValue.Address);
17 | });
18 |
19 | var app = builder.Build();
20 |
21 | // Configure the HTTP request pipeline.
22 | app.UseStaticFiles();
23 |
24 | app.UseRouting();
25 |
26 | app.UseAuthorization();
27 |
28 | app.UseEndpoints(endpoints =>
29 | {
30 | endpoints.MapControllerRoute(
31 | name: "default",
32 | pattern: "{controller=Home}/{action=Index}/{id?}");
33 | });
34 |
35 | app.Run();
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Settings/StockServiceSettings.cs:
--------------------------------------------------------------------------------
1 | namespace StockWeb.Settings;
2 | public class StockDataSettings
3 | {
4 | public string Address { get; set; }
5 | }
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/StockWeb.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 | Protos\stocks.proto
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model IndexViewModel
2 | @{
3 | ViewData["Title"] = "Home Page";
4 | }
5 |
6 |
7 |
NASDAQ 100
8 |
9 |
10 |
11 |
12 |
13 |
14 | Symbol
15 | Name
16 | Server
17 |
18 |
19 |
20 | @foreach (var stock in Model.Stocks)
21 | {
22 |
23 | @stock.Symbol
24 | @stock.Name
25 | @stock.ServerId
26 |
27 | }
28 |
29 |
30 |
32 |
33 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/Home/Privacy.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Privacy Policy";
3 | }
4 | @ViewData["Title"]
5 |
6 | Use this page to detail your site's privacy policy.
7 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - StockWeb
7 |
8 |
9 |
10 |
11 |
32 |
33 |
34 | @RenderBody()
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 | @RenderSection("Scripts", required: false)
47 |
48 |
49 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using StockWeb
2 | @using StockWeb.Models
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | },
9 | "StockData": {
10 | "Address": "http://localhost:5000"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | /* Provide sufficient contrast against white background */
11 | a {
12 | color: #0366d6;
13 | }
14 |
15 | .btn-primary {
16 | color: #fff;
17 | background-color: #1b6ec2;
18 | border-color: #1861ac;
19 | }
20 |
21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
22 | color: #fff;
23 | background-color: #1b6ec2;
24 | border-color: #1861ac;
25 | }
26 |
27 | /* Sticky footer styles
28 | -------------------------------------------------- */
29 | html {
30 | font-size: 14px;
31 | }
32 | @media (min-width: 768px) {
33 | html {
34 | font-size: 16px;
35 | }
36 | }
37 |
38 | .border-top {
39 | border-top: 1px solid #e5e5e5;
40 | }
41 | .border-bottom {
42 | border-bottom: 1px solid #e5e5e5;
43 | }
44 |
45 | .box-shadow {
46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
47 | }
48 |
49 | button.accept-policy {
50 | font-size: 1rem;
51 | line-height: inherit;
52 | }
53 |
54 | /* Sticky footer styles
55 | -------------------------------------------------- */
56 | html {
57 | position: relative;
58 | min-height: 100%;
59 | }
60 |
61 | body {
62 | /* Margin bottom by footer height */
63 | margin-bottom: 60px;
64 | }
65 | .footer {
66 | position: absolute;
67 | bottom: 0;
68 | width: 100%;
69 | white-space: nowrap;
70 | line-height: 60px; /* Vertically center the text there */
71 | }
72 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-architecture/grpc-for-wcf-developers/6d25545d4767daf55841e266d2f32499bce6d5a4/KubernetesSample/src/StockWeb/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2018 Twitter, Inc.
4 | Copyright (c) 2011-2018 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/KubernetesSample/src/StockWeb/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.PortfolioData/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections.Generic;
3 | global using System.Linq;
4 | global using System.Threading.Tasks;
5 | global using TraderSys.PortfolioData.Models;
6 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.PortfolioData/IPortfolioRepository.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.PortfolioData;
3 | public interface IPortfolioRepository
4 | {
5 | Task GetAsync(Guid traderId, int portfolioId);
6 | Task> GetAllAsync(Guid traderId);
7 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.PortfolioData/Models/Portfolio.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.PortfolioData.Models;
3 | public class Portfolio
4 | {
5 | public int Id { get; set; }
6 | public Guid TraderId { get; set; }
7 | public List Items { get; set; }
8 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.PortfolioData/Models/PortfolioItem.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.PortfolioData.Models;
2 | public class PortfolioItem
3 | {
4 | public int Id { get; set; }
5 | public int ShareId { get; set; }
6 | public int Holding { get; set; }
7 | public decimal Cost { get; set; }
8 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.PortfolioData/PortfolioRepository.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.PortfolioData;
3 | public class PortfolioRepository : IPortfolioRepository
4 | {
5 | public Task GetAsync(Guid traderId, int portfolioId)
6 | {
7 | return Task.FromResult(Fake(traderId, portfolioId));
8 | }
9 | public Task> GetAllAsync(Guid traderId)
10 | {
11 | var list = new List(4);
12 |
13 | // For test data, use Guid bytes as integer Id values
14 | var bytes = traderId.ToByteArray();
15 | for (int i = 0; i < 16; i += 4)
16 | {
17 | int id = BitConverter.ToInt32(bytes, i);
18 | list.Add(Fake(traderId, id));
19 | }
20 |
21 | return Task.FromResult(list);
22 | }
23 |
24 | private Portfolio Fake(Guid traderId, int portfolioId)
25 | {
26 | return new Portfolio
27 | {
28 | Id = portfolioId,
29 | TraderId = traderId,
30 | Items = FakeItems(portfolioId).ToList()
31 | };
32 | }
33 |
34 | private IEnumerable FakeItems(int portfolioId)
35 | {
36 | var random = new Random(portfolioId);
37 | int count = random.Next(2, 8);
38 | for (int i = 0; i < count; i++)
39 | {
40 | yield return new PortfolioItem
41 | {
42 | Id = random.Next(),
43 | ShareId = random.Next(),
44 | Cost = Convert.ToDecimal(random.NextDouble() * 10),
45 | Holding = random.Next(999999)
46 | };
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.PortfolioData/TraderSys.PortfolioData.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios.Client/TraderSys.Portfolios.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 | Protos\portfolios.proto
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios.ClientConsole/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Grpc.Core;
4 | using Grpc.Net.Client;
5 | using TraderSys.Portfolios.Protos;
6 |
7 | namespace TraderSys.Portfolios.ClientConsole;
8 | class Program
9 | {
10 | private const string ServerAddress = "https://localhost:5001";
11 |
12 | static async Task Main()
13 | {
14 | var channel = GrpcChannel.ForAddress(ServerAddress);
15 | var portfolios = new Protos.Portfolios.PortfoliosClient(channel);
16 |
17 | try
18 | {
19 | var request = new GetRequest
20 | {
21 | TraderId = "68CB16F7-42BD-4330-A191-FA5904D2E5A0",
22 | PortfolioId = 42
23 | };
24 | var response = await portfolios.GetAsync(request);
25 |
26 | Console.WriteLine($"Portfolio contains {response.Portfolio.Items.Count} items.");
27 | }
28 | catch (RpcException e)
29 | {
30 | Console.WriteLine(e.ToString());
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios.ClientConsole/TraderSys.Portfolios.ClientConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 |
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 |
19 | Protos\portfolios.proto
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Grpc.Core;
2 | global using Microsoft.AspNetCore.Builder;
3 | global using Microsoft.AspNetCore.Hosting;
4 | global using Microsoft.AspNetCore.Http;
5 | global using Microsoft.Extensions.DependencyInjection;
6 | global using Microsoft.Extensions.Hosting;
7 | global using System;
8 | global using System.Linq;
9 | global using System.Threading.Tasks;
10 | global using TraderSys.PortfolioData;
11 | global using TraderSys.Portfolios.Protos;
12 | global using TraderSys.Portfolios.Services;
13 |
14 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/Program.cs:
--------------------------------------------------------------------------------
1 |
2 | var builder = WebApplication.CreateBuilder(args);
3 |
4 | // Additional configuration is required to successfully run gRPC on macOS.
5 | // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
6 |
7 | // Add services to the container.
8 |
9 | // Register the repository class as a scoped service (instance per request)
10 | builder.Services.AddScoped();
11 |
12 | builder.Services.AddGrpc();
13 |
14 | var app = builder.Build();
15 |
16 | // Configure the HTTP request pipeline.
17 | app.MapGrpcService();
18 | app.MapGet("/", async context =>
19 | {
20 | await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
21 | });
22 |
23 | app.Run();
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/Protos/Portfolio.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios.Protos;
3 | public partial class Portfolio
4 | {
5 | public static Portfolio FromRepositoryModel(PortfolioData.Models.Portfolio source)
6 | {
7 | if (source is null) return null;
8 |
9 | var target = new Portfolio
10 | {
11 | Id = source.Id,
12 | TraderId = source.TraderId.ToString(),
13 | };
14 |
15 | target.Items.AddRange(source.Items.Select(PortfolioItem.FromRepositoryModel));
16 |
17 | return target;
18 | }
19 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/Protos/PortfolioItem.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios.Protos;
3 | public partial class PortfolioItem
4 | {
5 | public static PortfolioItem FromRepositoryModel(PortfolioData.Models.PortfolioItem source)
6 | {
7 | if (source is null) return null;
8 |
9 | return new PortfolioItem
10 | {
11 | Id = source.Id,
12 | ShareId = source.ShareId,
13 | Holding = source.Holding,
14 | Cost = Convert.ToDouble(source.Cost)
15 | };
16 | }
17 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/Protos/portfolios.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "TraderSys.Portfolios.Protos";
4 |
5 | package PortfolioService;
6 |
7 | message PortfolioItem {
8 | int32 id = 1;
9 | int32 shareId = 2;
10 | int32 holding = 3;
11 | double cost = 4;
12 | }
13 |
14 | message Portfolio {
15 | int32 id = 1;
16 | string traderId = 2;
17 | repeated PortfolioItem items = 3;
18 | }
19 |
20 | message GetRequest {
21 | string traderId = 1;
22 | int32 portfolioId = 2;
23 | }
24 |
25 | message GetResponse {
26 | Portfolio portfolio = 1;
27 | }
28 |
29 | message GetAllRequest {
30 | string traderId = 1;
31 | }
32 |
33 | message GetAllResponse {
34 | repeated Portfolio portfolios = 1;
35 | }
36 |
37 | service Portfolios {
38 | rpc Get(GetRequest) returns (GetResponse);
39 | rpc GetAll(GetAllRequest) returns (GetAllResponse);
40 | }
41 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/Services/PortfolioService.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios.Services;
3 | public class PortfolioService : Protos.Portfolios.PortfoliosBase
4 | {
5 | private readonly IPortfolioRepository _repository;
6 |
7 | public PortfolioService(IPortfolioRepository repository)
8 | {
9 | _repository = repository;
10 | }
11 |
12 | public override async Task Get(GetRequest request, ServerCallContext context)
13 | {
14 | if (!Guid.TryParse(request.TraderId, out var traderId))
15 | {
16 | throw new RpcException(new Status(StatusCode.InvalidArgument, "traderId must be a UUID"));
17 | }
18 |
19 | var portfolio = await _repository.GetAsync(traderId, request.PortfolioId);
20 |
21 | return new GetResponse
22 | {
23 | Portfolio = Portfolio.FromRepositoryModel(portfolio)
24 | };
25 | }
26 |
27 | public override async Task GetAll(GetAllRequest request, ServerCallContext context)
28 | {
29 | if (!Guid.TryParse(request.TraderId, out var traderId))
30 | {
31 | throw new RpcException(new Status(StatusCode.InvalidArgument, "traderId must be a UUID"));
32 | }
33 |
34 | var portfolios = await _repository.GetAllAsync(traderId);
35 |
36 | var response = new GetAllResponse();
37 | response.Portfolios.AddRange(portfolios.Select(Portfolio.FromRepositoryModel));
38 |
39 | return response;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/TraderSys.Portfolios.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Grpc": "Information",
7 | "Microsoft": "Information"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSys/src/TraderSys.Portfolios/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "Kestrel": {
10 | "EndpointDefaults": {
11 | "Protocols": "Http2"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.PortfolioData/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections.Generic;
3 | global using System.Linq;
4 | global using System.Threading.Tasks;
5 | global using TraderSys.PortfolioData.Models;
6 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.PortfolioData/IPortfolioRepository.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.PortfolioData;
3 | public interface IPortfolioRepository
4 | {
5 | Task GetAsync(Guid traderId, int portfolioId);
6 | Task> GetAllAsync(Guid traderId);
7 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.PortfolioData/Models/Portfolio.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.PortfolioData.Models;
3 | public class Portfolio
4 | {
5 | public int Id { get; set; }
6 | public Guid TraderId { get; set; }
7 | public List Items { get; set; }
8 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.PortfolioData/Models/PortfolioItem.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.PortfolioData.Models;
2 | public class PortfolioItem
3 | {
4 | public int Id { get; set; }
5 | public int ShareId { get; set; }
6 | public int Holding { get; set; }
7 | public decimal Cost { get; set; }
8 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.PortfolioData/PortfolioRepository.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.PortfolioData;
3 | public class PortfolioRepository : IPortfolioRepository
4 | {
5 | public Task GetAsync(Guid traderId, int portfolioId)
6 | {
7 | return Task.FromResult(Fake(traderId, portfolioId));
8 | }
9 | public Task> GetAllAsync(Guid traderId)
10 | {
11 | var list = new List(4);
12 |
13 | // For test data, use Guid bytes as integer Id values
14 | var bytes = traderId.ToByteArray();
15 | for (int i = 0; i < 16; i += 4)
16 | {
17 | int id = BitConverter.ToInt32(bytes, i);
18 | list.Add(Fake(traderId, id));
19 | }
20 |
21 | return Task.FromResult(list);
22 | }
23 |
24 | private Portfolio Fake(Guid traderId, int portfolioId)
25 | {
26 | return new Portfolio
27 | {
28 | Id = portfolioId,
29 | TraderId = traderId,
30 | Items = FakeItems(portfolioId).ToList()
31 | };
32 | }
33 |
34 | private IEnumerable FakeItems(int portfolioId)
35 | {
36 | var random = new Random(portfolioId);
37 | int count = random.Next(2, 8);
38 | for (int i = 0; i < count; i++)
39 | {
40 | yield return new PortfolioItem
41 | {
42 | Id = random.Next(),
43 | ShareId = random.Next(),
44 | Cost = Convert.ToDecimal(random.NextDouble() * 10),
45 | Holding = random.Next(999999)
46 | };
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.PortfolioData/TraderSys.PortfolioData.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios.Client/TraderSys.Portfolios.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 | Protos\portfolios.proto
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios.ClientConsole/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Grpc.Core;
5 | using Grpc.Net.Client;
6 | using TraderSys.Portfolios.Protos;
7 |
8 | namespace TraderSys.Portfolios.ClientConsole;
9 | class Program
10 | {
11 | private const string ServerAddress = "https://localhost:5001";
12 |
13 | static async Task Main(string[] args)
14 | {
15 | if (args.Length == 0)
16 | {
17 | Console.WriteLine("Specify trader name.");
18 | return;
19 | }
20 |
21 | var token = await Authenticate(args[0]);
22 |
23 | var channel = GrpcChannel.ForAddress(ServerAddress, new GrpcChannelOptions
24 | {
25 | Credentials = ChannelCredentials.Create(new SslCredentials(), CallCredentials.FromInterceptor((context, metadata) =>
26 | {
27 | metadata.Add("Authorization", $"Bearer {token}");
28 | return Task.CompletedTask;
29 | }))
30 | });
31 | var portfolios = new Protos.Portfolios.PortfoliosClient(channel);
32 |
33 | try
34 | {
35 | var request = new GetRequest
36 | {
37 | TraderId = "68CB16F7-42BD-4330-A191-FA5904D2E5A0",
38 | PortfolioId = 42
39 | };
40 | var response = await portfolios.GetAsync(request);
41 |
42 | Console.WriteLine($"Portfolio contains {response.Portfolio.Items.Count} items.");
43 | }
44 | catch (RpcException e)
45 | {
46 | if (e.StatusCode == StatusCode.PermissionDenied)
47 | {
48 | Console.WriteLine("Permission denied.");
49 | }
50 | }
51 | }
52 |
53 | static async Task Authenticate(string name)
54 | {
55 | using var client = new HttpClient();
56 | var request = new HttpRequestMessage
57 | {
58 | RequestUri = new Uri($"{ServerAddress}/generateJwtToken?name={name}"),
59 | Method = HttpMethod.Get,
60 | Version = new Version(2, 0),
61 | };
62 |
63 | var response = await client.SendAsync(request);
64 | response.EnsureSuccessStatusCode();
65 |
66 | return await response.Content.ReadAsStringAsync();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios.ClientConsole/TraderSys.Portfolios.ClientConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 |
19 | Protos\portfolios.proto
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.AspNetCore.Authentication.JwtBearer;
2 | global using Microsoft.AspNetCore.Builder;
3 | global using Microsoft.AspNetCore.Hosting;
4 | global using Microsoft.AspNetCore.Http;
5 | global using Microsoft.Extensions.DependencyInjection;
6 | global using Microsoft.Extensions.Hosting;
7 | global using Microsoft.IdentityModel.Tokens;
8 | global using System;
9 | global using System.Collections.Generic;
10 | global using System.IdentityModel.Tokens.Jwt;
11 | global using System.IO;
12 | global using System.Linq;
13 | global using System.Security.Claims;
14 | global using System.Threading.Tasks;
15 | global using TraderSys.PortfolioData;
16 | global using TraderSys.Portfolios.Services;
17 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/IUserLookup.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios;
3 | public interface IUserLookup
4 | {
5 | bool TryGetId(string name, out Guid guid);
6 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/JwtHelper.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios;
3 | internal static class JwtHelper
4 | {
5 | public static string GenerateJwtToken(string name)
6 | {
7 | if (string.IsNullOrEmpty(name))
8 | {
9 | throw new InvalidOperationException("Name is not specified.");
10 | }
11 |
12 | var claims = new[] { new Claim(ClaimTypes.Name, name) };
13 | var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
14 | var token = new JwtSecurityToken("ExampleServer", "ExampleClients", claims, expires: DateTime.Now.AddSeconds(60), signingCredentials: credentials);
15 | return JwtTokenHandler.WriteToken(token);
16 | }
17 |
18 | public static readonly JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler();
19 | public static readonly SymmetricSecurityKey SecurityKey = new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
20 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/Program.cs:
--------------------------------------------------------------------------------
1 |
2 | using TraderSys.Portfolios;
3 |
4 | var builder = WebApplication.CreateBuilder(args);
5 |
6 | // Additional configuration is required to successfully run gRPC on macOS.
7 | // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
8 |
9 | // Add services to the container.
10 |
11 | // Register the repository class as a scoped service (instance per request)
12 | builder.Services.AddScoped();
13 | builder.Services.AddSingleton();
14 |
15 | builder.Services.AddGrpc();
16 | builder.Services.AddAuthorization(options =>
17 | {
18 | options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
19 | {
20 | policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
21 | policy.RequireClaim(ClaimTypes.Name);
22 | });
23 | });
24 | builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
25 | .AddJwtBearer(options =>
26 | {
27 | options.TokenValidationParameters =
28 | new TokenValidationParameters
29 | {
30 | ValidateAudience = false,
31 | ValidateIssuer = false,
32 | ValidateActor = false,
33 | ValidateLifetime = true,
34 | IssuerSigningKey = JwtHelper.SecurityKey
35 | };
36 | });
37 |
38 | var app = builder.Build();
39 | app.UseRouting();
40 |
41 | app.UseAuthentication();
42 | app.UseAuthorization();
43 | // Configure the HTTP request pipeline.
44 | app.MapGrpcService();
45 | app.MapGet("/generateJwtToken", async context =>
46 | {
47 | await context.Response.WriteAsync(JwtHelper.GenerateJwtToken(context.Request.Query["name"]));
48 | });
49 |
50 | app.Run();
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/Protos/Portfolio.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios.Protos;
3 | public partial class Portfolio
4 | {
5 | public static Portfolio FromRepositoryModel(PortfolioData.Models.Portfolio source)
6 | {
7 | if (source is null) return null;
8 |
9 | var target = new Portfolio
10 | {
11 | Id = source.Id,
12 | TraderId = source.TraderId.ToString(),
13 | };
14 |
15 | target.Items.AddRange(source.Items.Select(PortfolioItem.FromRepositoryModel));
16 |
17 | return target;
18 | }
19 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/Protos/PortfolioItem.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios.Protos;
3 | public partial class PortfolioItem
4 | {
5 | public static PortfolioItem FromRepositoryModel(PortfolioData.Models.PortfolioItem source)
6 | {
7 | if (source is null) return null;
8 |
9 | return new PortfolioItem
10 | {
11 | Id = source.Id,
12 | ShareId = source.ShareId,
13 | Holding = source.Holding,
14 | Cost = Convert.ToDouble(source.Cost)
15 | };
16 | }
17 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/Protos/portfolios.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "TraderSys.Portfolios.Protos";
4 |
5 | package PortfolioService;
6 |
7 | message PortfolioItem {
8 | int32 id = 1;
9 | int32 shareId = 2;
10 | int32 holding = 3;
11 | double cost = 4;
12 | }
13 |
14 | message Portfolio {
15 | int32 id = 1;
16 | string traderId = 2;
17 | repeated PortfolioItem items = 3;
18 | }
19 |
20 | message GetRequest {
21 | string traderId = 1;
22 | int32 portfolioId = 2;
23 | }
24 |
25 | message GetResponse {
26 | Portfolio portfolio = 1;
27 | }
28 |
29 | message GetAllRequest {
30 | string traderId = 1;
31 | }
32 |
33 | message GetAllResponse {
34 | repeated Portfolio portfolios = 1;
35 | }
36 |
37 | service Portfolios {
38 | rpc Get(GetRequest) returns (GetResponse);
39 | rpc GetAll(GetAllRequest) returns (GetAllResponse);
40 | }
41 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/Services/PortfolioService.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Microsoft.AspNetCore.Authorization;
3 | using TraderSys.Portfolios.Protos;
4 |
5 | namespace TraderSys.Portfolios.Services;
6 | public class PortfolioService : Protos.Portfolios.PortfoliosBase
7 | {
8 | private readonly IPortfolioRepository _repository;
9 | private readonly IUserLookup _userLookup;
10 |
11 | public PortfolioService(IPortfolioRepository repository, IUserLookup userLookup)
12 | {
13 | _repository = repository;
14 | _userLookup = userLookup;
15 | }
16 |
17 | [Authorize]
18 | public override async Task Get(GetRequest request, ServerCallContext context)
19 | {
20 | var traderId = ValidateUser(request.TraderId, context);
21 |
22 | var portfolio = await _repository.GetAsync(traderId, request.PortfolioId);
23 |
24 | return new GetResponse
25 | {
26 | Portfolio = Portfolio.FromRepositoryModel(portfolio)
27 | };
28 | }
29 |
30 | public override async Task GetAll(GetAllRequest request, ServerCallContext context)
31 | {
32 | var traderId = ValidateUser(request.TraderId, context);
33 |
34 | var portfolios = await _repository.GetAllAsync(traderId);
35 |
36 | var response = new GetAllResponse();
37 | response.Portfolios.AddRange(portfolios.Select(Portfolio.FromRepositoryModel));
38 |
39 | return response;
40 | }
41 |
42 | private Guid ValidateUser(string suppliedId, ServerCallContext context)
43 | {
44 | if (!Guid.TryParse(suppliedId, out var traderId))
45 | {
46 | throw new RpcException(new Status(StatusCode.InvalidArgument, "traderId must be a UUID"));
47 | }
48 |
49 | var user = context.GetHttpContext().User;
50 |
51 | if (!_userLookup.TryGetId(user.Identity.Name, out var knownGuid) || knownGuid != traderId)
52 | {
53 | throw new RpcException(new Status(StatusCode.PermissionDenied, "Permission denied."));
54 | }
55 |
56 | return traderId;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/TraderSys.Portfolios.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/UserLookup.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.Portfolios;
3 | public class UserLookup : IUserLookup
4 | {
5 | private static readonly Dictionary Guids = new Dictionary(StringComparer.OrdinalIgnoreCase)
6 | {
7 | ["Alice"] = new Guid("68CB16F7-42BD-4330-A191-FA5904D2E5A0"),
8 | ["Bob"] = new Guid("7CD3AFA4-0F11-4947-84E5-FE8194DB5C3D"),
9 | };
10 |
11 | public bool TryGetId(string name, out Guid guid) => Guids.TryGetValue(name, out guid);
12 | }
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Grpc": "Information",
7 | "Microsoft": "Information"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/PortfoliosSample/grpc/TraderSysAuth/src/TraderSys.Portfolios/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "Kestrel": {
10 | "EndpointDefaults": {
11 | "Protocols": "Http2"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.PortfolioData/IPortfolioRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using TraderSys.PortfolioData.Models;
5 |
6 | namespace TraderSys.PortfolioData
7 | {
8 | public interface IPortfolioRepository
9 | {
10 | Task GetAsync(Guid traderId, int portfolioId);
11 | Task> GetAllAsync(Guid traderId);
12 | }
13 | }
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.PortfolioData/Models/Portfolio.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 |
5 | namespace TraderSys.PortfolioData.Models
6 | {
7 | [DataContract]
8 | public class Portfolio
9 | {
10 | [DataMember]
11 | public int Id { get; set; }
12 |
13 | [DataMember]
14 | public Guid TraderId { get; set; }
15 |
16 | [DataMember]
17 | public List Items { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.PortfolioData/Models/PortfolioItem.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace TraderSys.PortfolioData.Models
4 | {
5 | [DataContract]
6 | public class PortfolioItem
7 | {
8 | [DataMember]
9 | public int Id { get; set; }
10 |
11 | [DataMember]
12 | public int ShareId { get; set; }
13 |
14 | [DataMember]
15 | public int Holding { get; set; }
16 |
17 | [DataMember]
18 | public decimal Cost { get; set; }
19 | }
20 | }
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.PortfolioData/PortfolioRepository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using TraderSys.PortfolioData.Models;
6 |
7 | namespace TraderSys.PortfolioData
8 | {
9 | public class PortfolioRepository : IPortfolioRepository
10 | {
11 | public Task GetAsync(Guid traderId, int portfolioId)
12 | {
13 | return Task.FromResult(Fake(traderId, portfolioId));
14 | }
15 | public Task> GetAllAsync(Guid traderId)
16 | {
17 | var list = new List(4);
18 |
19 | // For test data, use Guid bytes as integer Id values
20 | var bytes = traderId.ToByteArray();
21 | for (int i = 0; i < 16; i += 4)
22 | {
23 | int id = BitConverter.ToInt32(bytes, i);
24 | list.Add(Fake(traderId, id));
25 | }
26 |
27 | return Task.FromResult(list);
28 | }
29 |
30 | private Portfolio Fake(Guid traderId, int portfolioId)
31 | {
32 | return new Portfolio
33 | {
34 | Id = portfolioId,
35 | TraderId = traderId,
36 | Items = FakeItems(portfolioId).ToList()
37 | };
38 | }
39 |
40 | private IEnumerable FakeItems(int portfolioId)
41 | {
42 | var random = new Random(portfolioId);
43 | int count = random.Next(2, 8);
44 | for (int i = 0; i < count; i++)
45 | {
46 | yield return new PortfolioItem
47 | {
48 | Id = random.Next(),
49 | ShareId = random.Next(),
50 | Cost = Convert.ToDecimal(random.NextDouble() * 10),
51 | Holding = random.Next(999999)
52 | };
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.PortfolioData/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("TraderSys.PortfolioData")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("TraderSys.PortfolioData")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("6fe13589-5e8e-4cc8-b01c-3cfa449610bc")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.PortfolioData/TraderSys.PortfolioData.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {6FE13589-5E8E-4CC8-B01C-3CFA449610BC}
8 | Library
9 | Properties
10 | TraderSys.PortfolioData
11 | TraderSys.PortfolioData
12 | v4.7.2
13 | 512
14 | true
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {D04790B7-617C-4955-B26B-DE6595A0C685}
54 | TraderSys.Models
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/App_Code/AppStart.cs:
--------------------------------------------------------------------------------
1 | using Autofac;
2 | using Autofac.Integration.Wcf;
3 | using TraderSys.PortfolioData;
4 |
5 | namespace TraderSys
6 | {
7 | public static class AppStart
8 | {
9 | public static void AppInitialize()
10 | {
11 | var builder = new ContainerBuilder();
12 | builder.RegisterType();
13 | builder.RegisterType().As();
14 | AutofacHostFactory.Container = builder.Build();
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/IPortfolioService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ServiceModel;
4 | using System.Threading.Tasks;
5 | using TraderSys.PortfolioData.Models;
6 |
7 | namespace TraderSys
8 | {
9 | [ServiceContract]
10 | public interface IPortfolioService
11 | {
12 | [OperationContract]
13 | Task Get(Guid traderId, int portfolioId);
14 |
15 | [OperationContract]
16 | Task> GetAll(Guid traderId);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/PortfolioService.svc:
--------------------------------------------------------------------------------
1 | <%@ ServiceHost Language="C#" Debug="true"
2 | Service="TraderSys.PortfolioService, TraderSys"
3 | CodeBehind="PortfolioService.svc.cs"
4 | Factory="Autofac.Integration.Wcf.AutofacServiceHostFactory, Autofac.Integration.Wcf"
5 | %>
6 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/PortfolioService.svc.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using TraderSys.PortfolioData;
5 | using TraderSys.PortfolioData.Models;
6 |
7 | namespace TraderSys
8 | {
9 | public class PortfolioService : IPortfolioService
10 | {
11 | private readonly IPortfolioRepository _repository;
12 |
13 | public PortfolioService(IPortfolioRepository repository)
14 | {
15 | _repository = repository;
16 | }
17 |
18 | public async Task Get(Guid traderId, int portfolioId)
19 | {
20 | return await _repository.GetAsync(traderId, portfolioId);
21 | }
22 |
23 | public async Task> GetAll(Guid traderId)
24 | {
25 | return await _repository.GetAllAsync(traderId);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("TraderSys")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("TraderSys")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0d9458f0-22ed-4de5-b846-628d58b1aa58")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Revision and Build Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/Web.Debug.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
29 |
30 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/Web.Release.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/Web.config:
--------------------------------------------------------------------------------
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 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.Portfolios/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/PortfoliosSample/wcf/TraderSys/TraderSys.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29306.81
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraderSys.PortfolioData", "TraderSys.PortfolioData\TraderSys.PortfolioData.csproj", "{6FE13589-5E8E-4CC8-B01C-3CFA449610BC}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraderSys.Portfolios", "TraderSys.Portfolios\TraderSys.Portfolios.csproj", "{0D9458F0-22ED-4DE5-B846-628D58B1AA58}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {6FE13589-5E8E-4CC8-B01C-3CFA449610BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {6FE13589-5E8E-4CC8-B01C-3CFA449610BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {6FE13589-5E8E-4CC8-B01C-3CFA449610BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {6FE13589-5E8E-4CC8-B01C-3CFA449610BC}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {0D9458F0-22ED-4DE5-B846-628D58B1AA58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {0D9458F0-22ED-4DE5-B846-628D58B1AA58}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {0D9458F0-22ED-4DE5-B846-628D58B1AA58}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {0D9458F0-22ED-4DE5-B846-628D58B1AA58}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {1C479F64-E7FB-4814-ACAA-EEA96C8473DB}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GRPC for WCF Developers Sample Reference Application
2 |
3 | Sample .NET 7.0 GRPC reference application, powered by Microsoft, based on latest GRPC .NET packages and Docker containers.
4 |
5 | ## Read the book
6 |
7 | The samples in this repository are meant to support the free eBook, [gRPC for WCF Developers](https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/).
8 |
9 | ## Version
10 |
11 | The current version uses .NET 7 and the latest gRPC libraries as of early 2023.
12 |
13 | ## Release Notes
14 |
15 | You'll find [additional release notes for major changes in the wiki](https://github.com/dotnet-architecture/grpc-for-wcf-developers/wiki/Release-notes).
16 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer.Client/TraderSys.SimpleStockTickerServer.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 | Protos\simple_stock_ticker.proto
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer.ClientConsole/Program.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Grpc.Net.Client;
3 | using System;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using TraderSys.SimpleStockTickerServer.Protos;
7 |
8 | namespace TraderSys.SimpleStockTickerServer.ClientConsole;
9 | class Program
10 | {
11 | static async Task Main(string[] args)
12 | {
13 | using var channel = GrpcChannel.ForAddress("https://localhost:5001");
14 | var client = new SimpleStockTicker.SimpleStockTickerClient(channel);
15 |
16 | var request = new SubscribeRequest();
17 | request.Symbols.AddRange(args);
18 | using var stream = client.Subscribe(request);
19 |
20 | var tokenSource = new CancellationTokenSource();
21 | var task = DisplayAsync(stream.ResponseStream, tokenSource.Token);
22 |
23 | WaitForExitKey();
24 |
25 | tokenSource.Cancel();
26 | await task;
27 | }
28 |
29 | static async Task DisplayAsync(IAsyncStreamReader stream, CancellationToken token)
30 | {
31 | try
32 | {
33 | await foreach (var update in stream.ReadAllAsync(token))
34 | {
35 | Console.WriteLine($"{update.Symbol}: {update.Price}");
36 | }
37 | }
38 | catch (RpcException e)
39 | {
40 | if (e.StatusCode == StatusCode.Cancelled)
41 | {
42 | return;
43 | }
44 | }
45 | catch (OperationCanceledException)
46 | {
47 | Console.WriteLine("Finished.");
48 | }
49 | }
50 |
51 | static void WaitForExitKey()
52 | {
53 | Console.WriteLine("Press E to exit...");
54 |
55 | char ch = ' ';
56 |
57 | while (ch != 'e')
58 | {
59 | ch = char.ToLowerInvariant(Console.ReadKey().KeyChar);
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer.ClientConsole/TraderSys.SimpleStockTickerServer.ClientConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Google.Protobuf.WellKnownTypes;
2 | global using Grpc.Core;
3 | global using Microsoft.AspNetCore.Builder;
4 | global using Microsoft.AspNetCore.Hosting;
5 | global using Microsoft.AspNetCore.Http;
6 | global using Microsoft.Extensions.DependencyInjection;
7 | global using Microsoft.Extensions.Hosting;
8 | global using Microsoft.Extensions.Logging;
9 | global using System;
10 | global using System.Linq;
11 | global using System.Threading;
12 | global using System.Threading.Tasks;
13 | global using TraderSys.SimpleStockTickerServer.Protos;
14 | global using TraderSys.SimpleStockTickerServer.Services;
15 | global using TraderSys.StockMarket;
16 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/Program.cs:
--------------------------------------------------------------------------------
1 |
2 | var builder = WebApplication.CreateBuilder(args);
3 |
4 | // Additional configuration is required to successfully run gRPC on macOS.
5 | // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
6 |
7 | // Add services to the container.
8 |
9 | // Register the repository class as a scoped service (instance per request)
10 | builder.Services.AddSingleton();
11 |
12 | builder.Services.AddGrpc();
13 |
14 | var app = builder.Build();
15 |
16 | // Configure the HTTP request pipeline.
17 | app.MapGrpcService();
18 | app.MapGet("/", async context =>
19 | {
20 | await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
21 | });
22 |
23 | app.Run();
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/Protos/simple_stock_ticker.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option csharp_namespace = "TraderSys.SimpleStockTickerServer.Protos";
4 |
5 | import "google/protobuf/timestamp.proto";
6 |
7 | package SimpleStockTickerServer;
8 |
9 | service SimpleStockTicker {
10 | rpc Subscribe (SubscribeRequest) returns (stream StockTickerUpdate);
11 | }
12 |
13 | message SubscribeRequest {
14 | repeated string symbols = 1;
15 | }
16 |
17 | message StockTickerUpdate {
18 | string symbol = 1;
19 | double price = 2;
20 | google.protobuf.Timestamp time = 3;
21 | }
22 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/Services/StockTickerService.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace TraderSys.SimpleStockTickerServer.Services;
3 | public class StockTickerService : Protos.SimpleStockTicker.SimpleStockTickerBase
4 | {
5 | private readonly IStockPriceSubscriberFactory _subscriberFactory;
6 | private readonly ILogger _logger;
7 |
8 | public StockTickerService(IStockPriceSubscriberFactory subscriberFactory, ILogger logger)
9 | {
10 | _subscriberFactory = subscriberFactory;
11 | _logger = logger;
12 | }
13 |
14 | public override async Task Subscribe(SubscribeRequest request,
15 | IServerStreamWriter responseStream, ServerCallContext context)
16 | {
17 | var subscriber = _subscriberFactory.GetSubscriber(request.Symbols.ToArray());
18 |
19 | subscriber.Update += async (sender, args) =>
20 | await WriteUpdateAsync(responseStream, args.Symbol, args.Price);
21 |
22 | _logger.LogInformation("Subscription started.");
23 |
24 | await AwaitCancellation(context.CancellationToken);
25 |
26 | subscriber.Dispose();
27 |
28 | _logger.LogInformation("Subscription finished.");
29 | }
30 |
31 | private async Task WriteUpdateAsync(IServerStreamWriter stream, string symbol, decimal price)
32 | {
33 | try
34 | {
35 | await stream.WriteAsync(new StockTickerUpdate
36 | {
37 | Symbol = symbol,
38 | Price = Convert.ToDouble(price),
39 | Time = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow)
40 | });
41 | }
42 | catch (Exception e)
43 | {
44 | _logger.LogError($"Failed to write message: {e.Message}");
45 | }
46 | }
47 |
48 | private static Task AwaitCancellation(CancellationToken token)
49 | {
50 | var completion = new TaskCompletionSource();
51 | token.Register(() => completion.SetResult(null));
52 | return completion.Task;
53 | }
54 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/TraderSys.SimpleStockTickerServer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Grpc": "Information",
7 | "Microsoft": "Information"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.SimpleStockTickerServer/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning",
5 | "Microsoft.Hosting.Lifetime": "Information"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "Kestrel": {
10 | "EndpointDefaults": {
11 | "Protocols": "Http2"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/FullStockPriceSubscriber.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public class FullStockPriceSubscriber : IDisposable, IFullStockPriceSubscriber
3 | {
4 | private readonly HashSet _prices = new HashSet();
5 | private readonly CancellationTokenSource _cancellationTokenSource;
6 | private readonly Task _task;
7 | private readonly Random _random;
8 |
9 | public FullStockPriceSubscriber()
10 | {
11 | _random = new Random(42);
12 | _cancellationTokenSource = new CancellationTokenSource();
13 | _task = RunAsync(_cancellationTokenSource.Token);
14 | }
15 |
16 | public event EventHandler Update;
17 |
18 | public void Add(string symbol)
19 | {
20 | _prices.Add(new StockPrice(symbol, _random.Next(99999) / 10m));
21 | }
22 |
23 | public void Remove(string symbol)
24 | {
25 | _prices.RemoveWhere(p => p.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase));
26 | }
27 |
28 | private async Task RunAsync(CancellationToken token)
29 | {
30 | try
31 | {
32 | while (!token.IsCancellationRequested)
33 | {
34 | await Task.Delay(_random.Next(1024, 8192), token);
35 | if (Update == null || _prices.Count == 0) continue;
36 |
37 | var price = _prices.Skip(_random.Next(_prices.Count)).First();
38 |
39 | var newPrice = price.Price + _random.Next(-99, 99) / 10m;
40 | if (newPrice < 0) newPrice = 0m;
41 | price.Price = newPrice;
42 |
43 | Update?.Invoke(this, new StockPriceUpdateEventArgs(price.Symbol, price.Price));
44 | }
45 | }
46 | catch (OperationCanceledException)
47 | {
48 | // Ignored
49 | }
50 | }
51 |
52 | public void Dispose()
53 | {
54 | _cancellationTokenSource.Cancel();
55 | }
56 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections.Generic;
3 | global using System.Linq;
4 | global using System.Threading;
5 | global using System.Threading.Tasks;
6 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/IFullStockPriceSubscriber.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public interface IFullStockPriceSubscriber : IDisposable
3 | {
4 | event EventHandler Update;
5 |
6 | void Add(string symbol);
7 | void Remove(string symbol);
8 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/IFullStockPriceSubscriberFactory.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public interface IFullStockPriceSubscriberFactory
3 | {
4 | IFullStockPriceSubscriber GetSubscriber();
5 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/IStockPriceSubscriber.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public interface IStockPriceSubscriber : IDisposable
3 | {
4 | event EventHandler Update;
5 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/IStockPriceSubscriberFactory.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public interface IStockPriceSubscriberFactory
3 | {
4 | IStockPriceSubscriber GetSubscriber(string[] symbols);
5 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/StockPrice.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | internal class StockPrice : IEquatable
3 | {
4 | public StockPrice(string symbol, decimal price)
5 | {
6 | Symbol = symbol;
7 | Price = price;
8 | }
9 |
10 | public string Symbol { get; }
11 | public decimal Price { get; set; }
12 |
13 | public bool Equals(StockPrice other)
14 | {
15 | if (ReferenceEquals(null, other)) return false;
16 | if (ReferenceEquals(this, other)) return true;
17 | return string.Equals(Symbol, other.Symbol, StringComparison.OrdinalIgnoreCase);
18 | }
19 |
20 | public override bool Equals(object obj)
21 | {
22 | if (ReferenceEquals(null, obj)) return false;
23 | if (ReferenceEquals(this, obj)) return true;
24 | if (obj.GetType() != this.GetType()) return false;
25 | return Equals((StockPrice)obj);
26 | }
27 |
28 | public override int GetHashCode()
29 | {
30 | return (Symbol != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Symbol) : 0);
31 | }
32 |
33 | public static bool operator ==(StockPrice left, StockPrice right)
34 | {
35 | return Equals(left, right);
36 | }
37 |
38 | public static bool operator !=(StockPrice left, StockPrice right)
39 | {
40 | return !Equals(left, right);
41 | }
42 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/StockPriceSubscriber.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public class StockPriceSubscriber : IStockPriceSubscriber
3 | {
4 | private readonly StockPrice[] _prices;
5 | private readonly CancellationTokenSource _cancellationTokenSource;
6 | private readonly Task _task;
7 | private readonly Random _random;
8 |
9 | public StockPriceSubscriber(string[] symbols)
10 | {
11 | _random = new Random(42);
12 | _prices = symbols.Select(s => new StockPrice(s, _random.Next(99999) / 10m)).ToArray();
13 | _cancellationTokenSource = new CancellationTokenSource();
14 | _task = RunAsync(_cancellationTokenSource.Token);
15 | }
16 |
17 | public event EventHandler Update;
18 |
19 | private async Task RunAsync(CancellationToken token)
20 | {
21 | try
22 | {
23 | while (!token.IsCancellationRequested)
24 | {
25 | await Task.Delay(_random.Next(5000), token);
26 | if (Update != null)
27 | {
28 | var price = _prices[_random.Next(_prices.Length)];
29 | var newPrice = price.Price + _random.Next(-99, 99) / 10m;
30 | if (newPrice < 0) newPrice = 0m;
31 | price.Price = newPrice;
32 | Update?.Invoke(this, new StockPriceUpdateEventArgs(price.Symbol, price.Price));
33 | }
34 | }
35 | }
36 | catch (OperationCanceledException)
37 | {
38 | // Ignored
39 | }
40 | }
41 |
42 | public void Dispose()
43 | {
44 | _cancellationTokenSource.Cancel();
45 | }
46 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/StockPriceSubscriberFactory.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public class StockPriceSubscriberFactory : IStockPriceSubscriberFactory
3 | {
4 | public IStockPriceSubscriber GetSubscriber(string[] symbols)
5 | {
6 | return new StockPriceSubscriber(symbols);
7 | }
8 | }
9 |
10 | public class FullStockPriceSubscriberFactory : IFullStockPriceSubscriberFactory
11 | {
12 | public IFullStockPriceSubscriber GetSubscriber()
13 | {
14 | return new FullStockPriceSubscriber();
15 | }
16 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/StockPriceUpdateEventArgs.cs:
--------------------------------------------------------------------------------
1 | namespace TraderSys.StockMarket;
2 | public class StockPriceUpdateEventArgs : EventArgs
3 | {
4 | public StockPriceUpdateEventArgs(string symbol, decimal price)
5 | {
6 | Symbol = symbol;
7 | Price = price;
8 | }
9 |
10 | public string Symbol { get; }
11 | public decimal Price { get; }
12 | }
13 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/grpc/SimpleStockTicker/src/TraderSys.StockMarket/TraderSys.StockMarket.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerClient/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerClient/ISimpleStockTickerCallback.cs:
--------------------------------------------------------------------------------
1 | using System.ServiceModel;
2 |
3 | namespace SimpleStockPriceTickerServer
4 | {
5 | [ServiceContract]
6 | public interface ISimpleStockTickerCallback
7 | {
8 | [OperationContract(IsOneWay = true)]
9 | void Update(string symbol, decimal price);
10 | }
11 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerClient/ISimpleStockTickerService.cs:
--------------------------------------------------------------------------------
1 | using System.ServiceModel;
2 |
3 | namespace SimpleStockPriceTickerServer
4 | {
5 | [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ISimpleStockTickerCallback))]
6 | public interface ISimpleStockTickerService
7 | {
8 | [OperationContract(IsOneWay = true)]
9 | void Subscribe(string[] symbols);
10 | }
11 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerClient/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.ServiceModel;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using SimpleStockPriceTickerServer;
8 |
9 | namespace SimpleStockPriceTickerClient
10 | {
11 | class Program
12 | {
13 | static void Main(string[] args)
14 | {
15 | var binding = new NetTcpBinding(SecurityMode.None);
16 | var address = new EndpointAddress("net.tcp://localhost:12384/simplestockticker");
17 | var factory =
18 | new DuplexChannelFactory(typeof(SimpleStockTickerCallback), binding,
19 | address);
20 | var context = new InstanceContext(new SimpleStockTickerCallback());
21 | var server = factory.CreateChannel(context);
22 |
23 | server.Subscribe(new []{"MSFT", "AAPL"});
24 |
25 | Console.WriteLine("Press E to exit...");
26 |
27 | char ch = ' ';
28 |
29 | while (ch != 'e')
30 | {
31 | ch = char.ToLowerInvariant((char)Console.Read());
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerClient/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SimpleStockPriceTickerClient")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SimpleStockPriceTickerClient")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("33ef5058-8400-48e3-bb70-ab73d71c49a6")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerClient/SimpleStockTickerCallback.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using SimpleStockPriceTickerServer;
3 |
4 | namespace SimpleStockPriceTickerClient
5 | {
6 | class SimpleStockTickerCallback : ISimpleStockTickerCallback
7 | {
8 | public void Update(string symbol, decimal price)
9 | {
10 | Console.WriteLine($"{symbol}: {price}");
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerServer/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerServer/ISimpleStockTickerCallback.cs:
--------------------------------------------------------------------------------
1 | using System.ServiceModel;
2 |
3 | namespace SimpleStockPriceTickerServer
4 | {
5 | [ServiceContract]
6 | public interface ISimpleStockTickerCallback
7 | {
8 | [OperationContract(IsOneWay = true)]
9 | void Update(string symbol, decimal price);
10 | }
11 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerServer/ISimpleStockTickerService.cs:
--------------------------------------------------------------------------------
1 | using System.ServiceModel;
2 |
3 | namespace SimpleStockPriceTickerServer
4 | {
5 | [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ISimpleStockTickerCallback))]
6 | public interface ISimpleStockTickerService
7 | {
8 | [OperationContract(IsOneWay = true)]
9 | void Subscribe(string[] symbols);
10 | }
11 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerServer/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.ServiceModel;
5 | using System.ServiceModel.Description;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace SimpleStockPriceTickerServer
10 | {
11 | class Program
12 | {
13 | static void Main(string[] args)
14 | {
15 | var host = new ServiceHost(typeof(SimpleStockTickerService));
16 | var binding = new NetTcpBinding(SecurityMode.None);
17 | host.AddServiceEndpoint(typeof(ISimpleStockTickerService), binding,
18 | "net.tcp://localhost:12384/simplestockticker");
19 |
20 | host.Open();
21 |
22 | Console.WriteLine("Press E to exit...");
23 |
24 | char ch = ' ';
25 |
26 | while (ch != 'e')
27 | {
28 | ch = char.ToLowerInvariant((char)Console.Read());
29 | }
30 |
31 | host.Close();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerServer/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SimpleStockPriceTickerServer")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SimpleStockPriceTickerServer")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("a07ca560-af81-4c34-b4e9-58284611539a")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockPriceTickerServer/SimpleStockTickerService.cs:
--------------------------------------------------------------------------------
1 | using System.ServiceModel;
2 | using TraderSys.StockMarket;
3 |
4 | namespace SimpleStockPriceTickerServer
5 | {
6 | [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
7 | public class SimpleStockTickerService : ISimpleStockTickerService
8 | {
9 | private ISimpleStockTickerCallback _callback;
10 | private SimpleStockPriceSubscriber _subscriber;
11 |
12 | public void Subscribe(string[] symbols)
13 | {
14 | _callback = OperationContext.Current.GetCallbackChannel();
15 | _subscriber = new SimpleStockPriceSubscriber(symbols);
16 | _subscriber.Update += SubscriberOnUpdate;
17 | }
18 |
19 | private void SubscriberOnUpdate(object sender, StockPriceUpdateEventArgs e)
20 | {
21 | try
22 | {
23 | _callback.Update(e.Symbol, e.Price);
24 | }
25 | catch (CommunicationException)
26 | {
27 | _subscriber.Dispose();
28 | _subscriber = null;
29 | }
30 | }
31 |
32 | private ISimpleStockTickerCallback Callback =>
33 | OperationContext.Current.GetCallbackChannel();
34 | }
35 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/SimpleStockTicker.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29306.81
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleStockPriceTickerServer", "SimpleStockPriceTickerServer\SimpleStockPriceTickerServer.csproj", "{A07CA560-AF81-4C34-B4E9-58284611539A}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleStockPriceTickerClient", "SimpleStockPriceTickerClient\SimpleStockPriceTickerClient.csproj", "{33EF5058-8400-48E3-BB70-AB73D71C49A6}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraderSys.StockMarket", "TraderSys.StockMarket\TraderSys.StockMarket.csproj", "{FE07B0B4-FB08-461F-935C-8A21F4A40919}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {A07CA560-AF81-4C34-B4E9-58284611539A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {A07CA560-AF81-4C34-B4E9-58284611539A}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {A07CA560-AF81-4C34-B4E9-58284611539A}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {A07CA560-AF81-4C34-B4E9-58284611539A}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {33EF5058-8400-48E3-BB70-AB73D71C49A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {33EF5058-8400-48E3-BB70-AB73D71C49A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {33EF5058-8400-48E3-BB70-AB73D71C49A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {33EF5058-8400-48E3-BB70-AB73D71C49A6}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {FE07B0B4-FB08-461F-935C-8A21F4A40919}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {FE07B0B4-FB08-461F-935C-8A21F4A40919}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {FE07B0B4-FB08-461F-935C-8A21F4A40919}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {FE07B0B4-FB08-461F-935C-8A21F4A40919}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {BCFA5C73-0D46-45E3-B581-BB5C5D6114A2}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/TraderSys.StockMarket/FullStockPriceSubscriber.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace TraderSys.StockMarket
8 | {
9 | public class FullStockPriceSubscriber : IDisposable
10 | {
11 | private readonly HashSet _prices = new HashSet();
12 | private readonly CancellationTokenSource _cancellationTokenSource;
13 | private readonly Task _task;
14 | private readonly Random _random;
15 |
16 | public FullStockPriceSubscriber()
17 | {
18 | _random = new Random(42);
19 | _cancellationTokenSource = new CancellationTokenSource();
20 | _task = RunAsync(_cancellationTokenSource.Token);
21 | }
22 |
23 | public event EventHandler Update;
24 |
25 | public void Add(string symbol)
26 | {
27 | _prices.Add(new StockPrice(symbol, _random.Next(99999) / 10m));
28 | }
29 |
30 | public void Remove(string symbol)
31 | {
32 | _prices.RemoveWhere(p => p.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase));
33 | }
34 |
35 | private async Task RunAsync(CancellationToken token)
36 | {
37 | try
38 | {
39 | while (!token.IsCancellationRequested)
40 | {
41 | await Task.Delay(_random.Next(1024, 8192), token);
42 | if (Update == null || _prices.Count == 0) continue;
43 |
44 | var price = _prices.Skip(_random.Next(_prices.Count)).First();
45 |
46 | var newPrice = price.Price + _random.Next(-99, 99) / 10m;
47 | if (newPrice < 0) newPrice = 0m;
48 | price.Price = newPrice;
49 |
50 | Update?.Invoke(this, new StockPriceUpdateEventArgs(price.Symbol, price.Price));
51 | }
52 | }
53 | catch (OperationCanceledException)
54 | {
55 | // Ignored
56 | }
57 | }
58 |
59 | public void Dispose()
60 | {
61 | _cancellationTokenSource.Cancel();
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/TraderSys.StockMarket/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("TraderSys.StockMarket")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("TraderSys.StockMarket")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("fe07b0b4-fb08-461f-935c-8a21f4a40919")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/TraderSys.StockMarket/SimpleStockPriceSubscriber.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace TraderSys.StockMarket
7 | {
8 | public class SimpleStockPriceSubscriber : IDisposable
9 | {
10 | private readonly StockPrice[] _prices;
11 | private readonly CancellationTokenSource _cancellationTokenSource;
12 | private readonly Task _task;
13 | private readonly Random _random;
14 |
15 | public SimpleStockPriceSubscriber(string[] symbols)
16 | {
17 | _random = new Random(42);
18 | _prices = symbols.Select(s => new StockPrice(s, _random.Next(99999) / 10m)).ToArray();
19 | _cancellationTokenSource = new CancellationTokenSource();
20 | _task = RunAsync(_cancellationTokenSource.Token);
21 | }
22 |
23 | public event EventHandler Update;
24 |
25 | private async Task RunAsync(CancellationToken token)
26 | {
27 | try
28 | {
29 | while (!token.IsCancellationRequested)
30 | {
31 | await Task.Delay(_random.Next(5000), token);
32 | if (Update != null)
33 | {
34 | var price = _prices[_random.Next(_prices.Length)];
35 | var newPrice = price.Price + _random.Next(-99, 99) / 10m;
36 | if (newPrice < 0) newPrice = 0m;
37 | price.Price = newPrice;
38 | Update?.Invoke(this, new StockPriceUpdateEventArgs(price.Symbol, price.Price));
39 | }
40 | }
41 | }
42 | catch (OperationCanceledException)
43 | {
44 | // Ignored
45 | }
46 | }
47 |
48 | public void Dispose()
49 | {
50 | _cancellationTokenSource.Cancel();
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/TraderSys.StockMarket/StockPrice.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace TraderSys.StockMarket
4 | {
5 | internal class StockPrice : IEquatable
6 | {
7 | public StockPrice(string symbol, decimal price)
8 | {
9 | Symbol = symbol;
10 | Price = price;
11 | }
12 |
13 | public string Symbol { get; }
14 | public decimal Price { get; set; }
15 |
16 | public bool Equals(StockPrice other)
17 | {
18 | if (ReferenceEquals(null, other)) return false;
19 | if (ReferenceEquals(this, other)) return true;
20 | return string.Equals(Symbol, other.Symbol, StringComparison.OrdinalIgnoreCase);
21 | }
22 |
23 | public override bool Equals(object obj)
24 | {
25 | if (ReferenceEquals(null, obj)) return false;
26 | if (ReferenceEquals(this, obj)) return true;
27 | if (obj.GetType() != this.GetType()) return false;
28 | return Equals((StockPrice) obj);
29 | }
30 |
31 | public override int GetHashCode()
32 | {
33 | return (Symbol != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Symbol) : 0);
34 | }
35 |
36 | public static bool operator ==(StockPrice left, StockPrice right)
37 | {
38 | return Equals(left, right);
39 | }
40 |
41 | public static bool operator !=(StockPrice left, StockPrice right)
42 | {
43 | return !Equals(left, right);
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/TraderSys.StockMarket/StockPriceUpdateEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace TraderSys.StockMarket
6 | {
7 | public class StockPriceUpdateEventArgs : EventArgs
8 | {
9 | public StockPriceUpdateEventArgs(string symbol, decimal price)
10 | {
11 | Symbol = symbol;
12 | Price = price;
13 | }
14 |
15 | public string Symbol { get; }
16 | public decimal Price { get; }
17 | }
18 | }
--------------------------------------------------------------------------------
/SimpleStockTickerSample/wcf/SimpleStockTicker/TraderSys.StockMarket/TraderSys.StockMarket.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {FE07B0B4-FB08-461F-935C-8A21F4A40919}
8 | Library
9 | Properties
10 | TraderSys.StockMarket
11 | TraderSys.StockMarket
12 | v4.7.2
13 | 512
14 | true
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------