├── .bowerrc ├── app ├── app.component.html ├── main.ts ├── home │ ├── home.component.html │ └── home.component.ts ├── app.routes.ts ├── shared │ ├── services │ │ ├── config.service.ts │ │ ├── data.service.ts │ │ └── feed.service.ts │ ├── directives │ │ └── highlight.directive.ts │ ├── components │ │ ├── chat.component.ts │ │ ├── match.component.ts │ │ ├── chat.component.html │ │ └── match.component.html │ └── interfaces.ts ├── app.component.ts └── app.module.ts ├── Models ├── IEntityBase.cs ├── MatchTypeEnum.cs ├── MatchScore.cs ├── ChatMessage.cs ├── FeedViewModel.cs ├── Feed.cs ├── MatchViewModel.cs └── Match.cs ├── typings.json ├── Dockerfile ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── appsettings.json ├── bower.json ├── Data ├── Abstract │ ├── IRepositories.cs │ └── IEntityBaseRepository.cs ├── Repositories │ ├── FeedRepository.cs │ ├── MatchRepository.cs │ └── EntityBaseRepository.cs ├── LiveGameContext.cs └── LiveGameDbInitializer.cs ├── NuGet.config ├── Controllers ├── HomeController.cs ├── MessagesController.cs ├── ApiHubController.cs ├── FeedsController.cs └── MatchesController.cs ├── tsconfig.json ├── Core ├── Mappings │ ├── AutoMapperConfiguration.cs │ └── DomainToViewModelMappingProfile.cs └── FeedEngine.cs ├── web.config ├── appveyor.yml ├── Properties └── launchSettings.json ├── Program.cs ├── gulp.config.js ├── licence ├── Hubs └── Broadcaster.cs ├── package.json ├── wwwroot ├── css │ └── site.css └── js │ └── systemjs.config.js ├── gulpfile.js ├── aspnet-core-signalr-angular.csproj ├── Startup.cs ├── Views └── Home │ └── Index.cshtml ├── .gitignore └── README.md /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } -------------------------------------------------------------------------------- /app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /Models/IEntityBase.cs: -------------------------------------------------------------------------------- 1 | namespace LiveGameFeed.Models 2 | { 3 | public interface IEntityBase 4 | { 5 | int Id { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Models/MatchTypeEnum.cs: -------------------------------------------------------------------------------- 1 | namespace LiveGameFeed.Models 2 | { 3 | public enum MatchTypeEnums { 4 | Football, 5 | Basketball 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "jquery": "registry:dt/jquery", 4 | "signalr": "registry:dt/signalr", 5 | "jasmine": "registry:dt/jasmine" 6 | } 7 | } -------------------------------------------------------------------------------- /Models/MatchScore.cs: -------------------------------------------------------------------------------- 1 | namespace LiveGameFeed.Models 2 | { 3 | public class MatchScore 4 | { 5 | public int HostScore { get; set; } 6 | public int GuestScore {get; set;} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { AppModule } from './app.module'; 3 | 4 | const platform = platformBrowserDynamic(); 5 | 6 | platform.bootstrapModule(AppModule); 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:latest 2 | 3 | COPY . /app 4 | 5 | WORKDIR /app 6 | 7 | RUN ["dotnet", "restore"] 8 | 9 | RUN ["dotnet", "build"] 10 | 11 | EXPOSE 5000/tcp 12 | 13 | CMD ["dotnet", "run", "--server.urls", "http://*:5000"] 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "**/app/**/*.js": true, 7 | "**/*.map": true 8 | } 9 | } -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "apiURL" : "http://localhost:5000/api/" 11 | } 12 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "livegamefeed", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.5", 6 | "jquery": "2.1.4", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.4", 9 | "signalr": "2.2.0" 10 | }, 11 | "ignore": "" 12 | } -------------------------------------------------------------------------------- /Data/Abstract/IRepositories.cs: -------------------------------------------------------------------------------- 1 | using LiveGameFeed.Models; 2 | 3 | namespace LiveGameFeed.Data.Abstract 4 | { 5 | public interface IMatchRepository : IEntityBaseRepository { } 6 | 7 | public interface IFeedRepository : IEntityBaseRepository { } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Models/ChatMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LiveGameFeed.Models 4 | { 5 | public class ChatMessage 6 | { 7 | public int MatchId { get; set; } 8 | public string Text { get; set; } 9 | public DateTime CreatedAt {get; set;} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace LiveGameFeed.Controllers 4 | { 5 | public class HomeController : Controller 6 | { 7 | public HomeController() { } 8 | 9 | public IActionResult Index() 10 | { 11 | return View(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
-------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "removeComments": false, 10 | "noImplicitAny": false, 11 | "skipLibCheck": true 12 | } 13 | } -------------------------------------------------------------------------------- /Models/FeedViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LiveGameFeed.Models 4 | { 5 | public class FeedViewModel : IEntityBase 6 | { 7 | public int Id { get; set; } 8 | public string Description { get; set; } 9 | public DateTime CreatedAt { get; set; } 10 | public int MatchId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Data/Repositories/FeedRepository.cs: -------------------------------------------------------------------------------- 1 | using LiveGameFeed.Data.Abstract; 2 | using LiveGameFeed.Models; 3 | 4 | namespace LiveGameFeed.Data.Repositories 5 | { 6 | public class FeedRepository : EntityBaseRepository, IFeedRepository 7 | { 8 | public FeedRepository(LiveGameContext context) 9 | : base(context) 10 | { } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Data/Repositories/MatchRepository.cs: -------------------------------------------------------------------------------- 1 | using LiveGameFeed.Data.Abstract; 2 | using LiveGameFeed.Models; 3 | 4 | namespace LiveGameFeed.Data.Repositories 5 | { 6 | public class MatchRepository : EntityBaseRepository, IMatchRepository 7 | { 8 | public MatchRepository(LiveGameContext context) 9 | : base(context) 10 | { } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Models/Feed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LiveGameFeed.Models 4 | { 5 | public class Feed : IEntityBase 6 | { 7 | public int Id { get; set; } 8 | public string Description { get; set; } 9 | public DateTime CreatedAt { get; set; } 10 | public int MatchId { get; set; } 11 | public Match Match { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "${workspaceRoot}/project.json" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /Core/Mappings/AutoMapperConfiguration.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace LiveGameFeed.Core.Mappings 4 | { 5 | public class AutoMapperConfiguration 6 | { 7 | public static void Configure() 8 | { 9 | Mapper.Initialize(x => 10 | { 11 | x.AddProfile(); 12 | }); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { HomeComponent } from './home/home.component'; 5 | 6 | const appRoutes: Routes = [ 7 | { path: 'home', component: HomeComponent }, 8 | { path: '', component: HomeComponent } 9 | ]; 10 | 11 | export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes); -------------------------------------------------------------------------------- /app/shared/services/config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class ConfigService { 5 | 6 | _apiURI : string; 7 | 8 | constructor() { 9 | this._apiURI = 'api/'; 10 | } 11 | 12 | getApiURI() { 13 | return this._apiURI; 14 | } 15 | 16 | getApiHost() { 17 | return this._apiURI.replace('api/',''); 18 | } 19 | } -------------------------------------------------------------------------------- /Models/MatchViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LiveGameFeed.Models 5 | { 6 | public class MatchViewModel : IEntityBase 7 | { 8 | public int Id { get; set; } 9 | public string Host { get; set; } 10 | public string Guest { get; set; } 11 | public int HostScore { get; set; } 12 | public int GuestScore { get; set; } 13 | public DateTime MatchDate { get; set; } 14 | public string Type { get; set; } 15 | 16 | public ICollection Feeds {get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { FeedService } from './shared/services/feed.service'; 4 | import { SignalRConnectionStatus } from './shared/interfaces'; 5 | 6 | @Component({ 7 | selector: 'my-app', 8 | templateUrl: 'app/app.component.html', 9 | providers: [FeedService] 10 | }) 11 | export class AppComponent implements OnInit { 12 | 13 | constructor(private service: FeedService) { } 14 | 15 | ngOnInit() { 16 | this.service.start(true).subscribe( 17 | null, 18 | error => console.log('Error on init: ' + error)); 19 | } 20 | } -------------------------------------------------------------------------------- /Models/Match.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace LiveGameFeed.Models 5 | { 6 | public class Match : IEntityBase 7 | { 8 | public Match() 9 | { 10 | Feeds = new List(); 11 | } 12 | public int Id { get; set; } 13 | public string Host { get; set; } 14 | public string Guest { get; set; } 15 | public int HostScore { get; set; } 16 | public int GuestScore { get; set; } 17 | public DateTime MatchDate { get; set; } 18 | public MatchTypeEnums Type { get; set; } 19 | 20 | public ICollection Feeds { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | - 2 | branches: 3 | only: 4 | - master 5 | image: Visual Studio 2017 6 | # Test against the latest version of this Node.js version 7 | environment: 8 | nodejs_version: "6" 9 | DOTNET_CLI_TELEMETRY_OPTOUT: true 10 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 11 | 12 | # Install scripts. (runs after repo cloning) 13 | install: 14 | # Get the latest stable version of Node.js or io.js 15 | - ps: Install-Product node $env:nodejs_version 16 | # install modules 17 | - npm install 18 | 19 | version: 1.0.{build} 20 | configuration: Release 21 | platform: Any CPU 22 | skip_branch_with_pr: false 23 | before_build: 24 | - cmd: dotnet restore 25 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "LiveGameFeed": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "http://localhost:5000/", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Core/Mappings/DomainToViewModelMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using AutoMapper; 4 | using LiveGameFeed.Models; 5 | 6 | namespace LiveGameFeed.Core.Mappings 7 | { 8 | public class DomainToViewModelMappingProfile : Profile 9 | { 10 | protected override void Configure() 11 | { 12 | Mapper.CreateMap() 13 | .ForMember(vm => vm.Type, map => map.MapFrom(m => m.Type.ToString())) 14 | .ForMember(vm => vm.Feeds, map => map.MapFrom(m => 15 | Mapper.Map, ICollection>(m.Feeds.OrderByDescending(f => f.Id).ToList()))); 16 | Mapper.CreateMap(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Controllers/MessagesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.SignalR.Infrastructure; 3 | using LiveGameFeed.Hubs; 4 | using LiveGameFeed.Models; 5 | 6 | namespace LiveGameFeed.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | public class MessagesController : ApiHubController 10 | { 11 | public MessagesController( 12 | IConnectionManager signalRConnectionManager) 13 | : base(signalRConnectionManager) 14 | { 15 | 16 | } 17 | 18 | // POST api/messages 19 | [HttpPost] 20 | public void Post([FromBody]ChatMessage message) 21 | { 22 | this.Clients.Group(message.MatchId.ToString()).AddChatMessage(message); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Controllers/ApiHubController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.SignalR; 3 | using Microsoft.AspNetCore.SignalR.Infrastructure; 4 | using Microsoft.AspNetCore.SignalR.Hubs; 5 | 6 | namespace LiveGameFeed.Controllers 7 | { 8 | public abstract class ApiHubController : Controller 9 | where T : Hub 10 | { 11 | private readonly IHubContext _hub; 12 | public IHubConnectionContext Clients { get; private set; } 13 | public IGroupManager Groups { get; private set; } 14 | protected ApiHubController(IConnectionManager signalRConnectionManager) 15 | { 16 | var _hub = signalRConnectionManager.GetHubContext(); 17 | Clients = _hub.Clients; 18 | Groups = _hub.Groups; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace LiveGameFeed 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | var config = new ConfigurationBuilder() 13 | .AddCommandLine(args) 14 | .AddEnvironmentVariables(prefix: "ASPNETCORE_") 15 | .Build(); 16 | 17 | var host = new WebHostBuilder() 18 | .UseConfiguration(config) 19 | .UseKestrel() 20 | .UseContentRoot(Directory.GetCurrentDirectory()) 21 | .UseIISIntegration() 22 | .UseStartup() 23 | .Build(); 24 | 25 | host.Run(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/shared/directives/highlight.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostListener, Input, Renderer } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[feedHighlight]' 5 | }) 6 | export class HighlightDirective { 7 | constructor(private el: ElementRef, private renderer: Renderer) { 8 | let self = this; 9 | self.renderer.setElementClass(this.el.nativeElement, 'feed-highlight', true); 10 | let horn = document.getElementById('horn'); 11 | horn.className += ' orange'; 12 | setTimeout(function () { 13 | self.renderer.setElementClass(self.el.nativeElement, 'feed-highlight-light', true); 14 | 15 | document.getElementById("horn").className = document.getElementById("horn").className.replace(/(?:^|\s)orange(?!\S)/g, '') 16 | }, 1000); 17 | } 18 | 19 | private highlight(color: string) { 20 | this.renderer.setElementStyle(this.el.nativeElement, 'backgroundColor', color); 21 | } 22 | } -------------------------------------------------------------------------------- /Data/Abstract/IEntityBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using LiveGameFeed.Models; 5 | 6 | namespace LiveGameFeed.Data.Abstract 7 | { 8 | public interface IEntityBaseRepository where T : class, IEntityBase, new() 9 | { 10 | IEnumerable AllIncluding(params Expression>[] includeProperties); 11 | IEnumerable GetAll(); 12 | int Count(); 13 | T GetSingle(int id); 14 | T GetSingle(Expression> predicate); 15 | T GetSingle(Expression> predicate, params Expression>[] includeProperties); 16 | IEnumerable FindBy(Expression> predicate); 17 | void Add(T entity); 18 | void Update(T entity); 19 | void Delete(T entity); 20 | void DeleteWhere(Expression> predicate); 21 | void Commit(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/shared/components/chat.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | import { ChatMessage, Match } from '../interfaces'; 4 | import { FeedService } from '../services/feed.service'; 5 | 6 | @Component({ 7 | selector: 'chat', 8 | templateUrl: 'app/shared/components/chat.component.html' 9 | }) 10 | export class ChatComponent implements OnInit { 11 | 12 | @Input() matches: Match[]; 13 | @Input() connection: string; 14 | messages: ChatMessage[]; 15 | 16 | constructor(private feedService: FeedService) { } 17 | 18 | ngOnInit() { 19 | let self = this; 20 | 21 | self.feedService.addChatMessage.subscribe( 22 | message => { 23 | console.log('received..'); 24 | console.log(message); 25 | if(!self.messages) 26 | self.messages = new Array(); 27 | self.messages.unshift(message); 28 | } 29 | ) 30 | } 31 | } -------------------------------------------------------------------------------- /gulp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | 3 | var base = { 4 | webroot: "./wwwroot/", 5 | node_modules: "./node_modules/" 6 | }; 7 | 8 | var config = { 9 | /** 10 | * Files paths 11 | */ 12 | angular: base.node_modules + "@angular/**/*.js", 13 | app : "app/**/*.*", 14 | appDest : base.webroot + "app", 15 | js: base.webroot + "js/**/*.js", 16 | css : base.webroot + "css/**/*.css", 17 | lib : base.webroot + "lib/", 18 | node_modules : base.node_modules, 19 | angularWebApi : base.node_modules + "angular2-in-memory-web-api/*.js", 20 | corejs : base.node_modules + "core-js/client/shim*.js", 21 | zonejs : base.node_modules + "zone.js/dist/zone*.js", 22 | reflectjs : base.node_modules + "reflect-metadata/Reflect*.js", 23 | systemjs : base.node_modules + "systemjs/dist/*.js", 24 | rxjs : base.node_modules + "rxjs/**/*.js", 25 | jasminejs : base.node_modules + "jasmine-core/lib/jasmine-core/*.*" 26 | }; 27 | 28 | return config; 29 | }; -------------------------------------------------------------------------------- /Data/LiveGameContext.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using LiveGameFeed.Models; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | 6 | namespace LiveGameFeed.Data 7 | { 8 | public class LiveGameContext : DbContext 9 | { 10 | public DbSet Matches { get; set; } 11 | public DbSet Feeds { get; set; } 12 | 13 | public LiveGameContext(DbContextOptions options) : base(options) { } 14 | 15 | protected override void OnModelCreating(ModelBuilder modelBuilder) 16 | { 17 | foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys())) 18 | { 19 | relationship.DeleteBehavior = DeleteBehavior.Restrict; 20 | } 21 | 22 | modelBuilder.Entity() 23 | .ToTable("Match"); 24 | 25 | modelBuilder.Entity() 26 | .ToTable("Feed"); 27 | 28 | modelBuilder.Entity() 29 | .Property(f => f.MatchId) 30 | .IsRequired(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /licence: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Christos Sakellarios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Hubs/Broadcaster.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.SignalR; 3 | using LiveGameFeed.Models; 4 | 5 | namespace LiveGameFeed.Hubs 6 | { 7 | public class Broadcaster : Hub 8 | { 9 | public override Task OnConnected() 10 | { 11 | // Set connection id for just connected client only 12 | return Clients.Client(Context.ConnectionId).SetConnectionId(Context.ConnectionId); 13 | } 14 | 15 | // Server side methods called from client 16 | public Task Subscribe(int matchId) 17 | { 18 | return Groups.Add(Context.ConnectionId, matchId.ToString()); 19 | } 20 | 21 | public Task Unsubscribe(int matchId) 22 | { 23 | return Groups.Remove(Context.ConnectionId, matchId.ToString()); 24 | } 25 | } 26 | 27 | // Client side methods to be invoked by Broadcaster Hub 28 | public interface IBroadcaster 29 | { 30 | Task SetConnectionId(string connectionId); 31 | Task UpdateMatch(MatchViewModel match); 32 | Task AddFeed(FeedViewModel feed); 33 | Task AddChatMessage(ChatMessage message); 34 | } 35 | } -------------------------------------------------------------------------------- /app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { ConfigService } from './shared/services/config.service'; 8 | import { DataService } from './shared/services/data.service'; 9 | import { HomeComponent } from './home/home.component'; 10 | import { HighlightDirective } from './shared/directives/highlight.directive'; 11 | import { MatchComponent } from './shared/components/match.component'; 12 | import { ChatComponent } from './shared/components/chat.component'; 13 | import { routing } from './app.routes'; 14 | 15 | @NgModule({ 16 | imports: [ 17 | BrowserModule, 18 | FormsModule, 19 | HttpModule, 20 | routing 21 | ], 22 | declarations: [ 23 | AppComponent, 24 | ChatComponent, 25 | HomeComponent, 26 | HighlightDirective, 27 | MatchComponent 28 | ], 29 | bootstrap: [AppComponent], 30 | providers: [ 31 | ConfigService, 32 | DataService 33 | ] 34 | }) 35 | export class AppModule { } 36 | -------------------------------------------------------------------------------- /app/shared/interfaces.ts: -------------------------------------------------------------------------------- 1 | /* SignalR related interfaces */ 2 | export interface FeedSignalR extends SignalR { 3 | broadcaster: FeedProxy 4 | } 5 | 6 | export interface FeedProxy { 7 | client: FeedClient; 8 | server: FeedServer; 9 | } 10 | 11 | export interface FeedClient { 12 | setConnectionId: (id: string) => void; 13 | updateMatch: (match: Match) => void; 14 | addFeed: (feed: Feed) => void; 15 | addChatMessage: (chatMessage: ChatMessage) => void; 16 | } 17 | 18 | export interface FeedServer { 19 | subscribe(matchId: number): void; 20 | unsubscribe(matchId: number): void; 21 | } 22 | 23 | export enum SignalRConnectionStatus { 24 | Connected = 1, 25 | Disconnected = 2, 26 | Error = 3 27 | } 28 | 29 | /* LiveGameFeed related interfaces */ 30 | export interface Match { 31 | Id: number; 32 | Host: string; 33 | Guest: string; 34 | HostScore: number; 35 | GuestScore: number; 36 | MatchDate: Date; 37 | Type: string; 38 | Feeds: Feed[]; 39 | } 40 | 41 | export interface Feed { 42 | Id: number; 43 | Description: string; 44 | CreatedAt: Date; 45 | MatchId: number; 46 | } 47 | 48 | export interface ChatMessage { 49 | MatchId: number; 50 | Text: string; 51 | CreatedAt: Date; 52 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (web)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/aspnet-core-signalr-angular.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false, 13 | "internalConsoleOptions": "openOnSessionStart", 14 | "launchBrowser": { 15 | "enabled": true, 16 | "args": "${auto-detect-url}", 17 | "windows": { 18 | "command": "cmd.exe", 19 | "args": "/C start ${auto-detect-url}" 20 | }, 21 | "osx": { 22 | "command": "open" 23 | }, 24 | "linux": { 25 | "command": "xdg-open" 26 | } 27 | }, 28 | "env": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | }, 31 | "sourceFileMap": { 32 | "/Views": "${workspaceRoot}/Views" 33 | } 34 | }, 35 | { 36 | "name": ".NET Core Attach", 37 | "type": "coreclr", 38 | "request": "attach", 39 | "processId": "${command.pickProcess}" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Controllers/FeedsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.SignalR.Infrastructure; 3 | using LiveGameFeed.Hubs; 4 | using LiveGameFeed.Data.Abstract; 5 | using LiveGameFeed.Models; 6 | using AutoMapper; 7 | 8 | namespace LiveGameFeed.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class FeedsController : ApiHubController 12 | { 13 | IFeedRepository _feedRepository; 14 | IMatchRepository _matchRepository; 15 | public FeedsController( 16 | IConnectionManager signalRConnectionManager, 17 | IFeedRepository feedRepository, 18 | IMatchRepository matchRepository) 19 | : base(signalRConnectionManager) 20 | { 21 | _feedRepository = feedRepository; 22 | _matchRepository = matchRepository; 23 | } 24 | 25 | // POST api/feeds 26 | [HttpPost] 27 | public async void Post([FromBody]FeedViewModel feed) 28 | { 29 | Match _match = _matchRepository.GetSingle(feed.MatchId); 30 | Feed _matchFeed = new Feed() 31 | { 32 | Description = feed.Description, 33 | CreatedAt = feed.CreatedAt, 34 | MatchId = feed.MatchId 35 | }; 36 | 37 | _match.Feeds.Add(_matchFeed); 38 | 39 | _matchRepository.Commit(); 40 | 41 | FeedViewModel _feedVM = Mapper.Map(_matchFeed); 42 | 43 | await Clients.Group(feed.MatchId.ToString()).AddFeed(_feedVM); 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/shared/components/match.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | import { ChatMessage, Match } from '../interfaces'; 4 | import { FeedService } from '../services/feed.service'; 5 | import { DataService } from '../services/data.service'; 6 | 7 | @Component({ 8 | selector: 'match', 9 | templateUrl: 'app/shared/components/match.component.html' 10 | }) 11 | export class MatchComponent implements OnInit { 12 | 13 | @Input() match: Match; 14 | @Output() updateSubscription = new EventEmitter(); 15 | subscribed: boolean; 16 | chatMessage: string = ''; 17 | 18 | constructor(private dataService: DataService) { } 19 | 20 | ngOnInit() { } 21 | 22 | setSubscription(val: boolean) { 23 | this.subscribed = val; 24 | let subscription = 25 | { 26 | subscribe: val, 27 | matchId: this.match.Id 28 | } 29 | 30 | this.updateSubscription.emit(subscription); 31 | } 32 | 33 | addChatMessage() { 34 | let self = this; 35 | let messageToSend: ChatMessage = { 36 | MatchId: self.match.Id, 37 | Text: self.chatMessage, 38 | CreatedAt: new Date(Date.now()) 39 | }; 40 | 41 | this.dataService.addChatMessage(messageToSend) 42 | .subscribe(() => { 43 | // Nothing to do here 44 | // Since is subscribed, caller will also receive the message 45 | console.log('message sent..'); 46 | }, 47 | error => { 48 | console.log(error); 49 | }); 50 | 51 | self.chatMessage = ''; 52 | } 53 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "description": "live game feed", 4 | "name": "livegamefeed", 5 | "readme": "chsakell's blog all right reserved", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/chsakell/aspnet-core-signalr-angular" 10 | }, 11 | "dependencies": { 12 | "@angular/animations": "4.0.2", 13 | "@angular/common": "4.0.2", 14 | "@angular/compiler": "4.0.2", 15 | "@angular/core": "4.0.2", 16 | "@angular/forms": "4.0.2", 17 | "@angular/http": "4.0.2", 18 | "@angular/platform-browser": "4.0.2", 19 | "@angular/platform-browser-dynamic": "4.0.2", 20 | "@angular/router": "4.0.2", 21 | 22 | "angular-in-memory-web-api": "~0.2.4", 23 | "core-js": "^2.4.1", 24 | "reflect-metadata": "^0.1.8", 25 | "rxjs": "5.0.1", 26 | "systemjs": "0.19.40", 27 | "zone.js": "^0.8.5", 28 | 29 | "bower": "1.7.9", 30 | "jquery": "^3.1.0", 31 | "signalr": "^2.2.1" 32 | }, 33 | "devDependencies": { 34 | "concurrently": "^2.2.0", 35 | "gulp": ">=3.9.1", 36 | "gulp-concat": ">=2.5.2", 37 | "gulp-copy": ">=0.0.2", 38 | "gulp-cssmin": ">=0.1.7", 39 | "gulp-load-plugins": "^1.3.0", 40 | "gulp-rename": ">=1.2.2", 41 | "gulp-rimraf": ">=0.2.0", 42 | "gulp-tsc": ">=1.2.0", 43 | "gulp-uglify": ">=1.2.0", 44 | "gulp-watch": ">=4.3.9", 45 | "jasmine-core": "2.4.1", 46 | "tslint": "^3.15.1", 47 | "typescript": "^2.2.2", 48 | "typings": "^1.3.2" 49 | }, 50 | "scripts": { 51 | "start": "concurrently \"npm run gulp\" \"npm run watch\" \"npm run tsc:w\"", 52 | "postinstall": "typings install", 53 | "tsc": "tsc", 54 | "tsc:w": "tsc -w", 55 | "typings": "typings", 56 | "gulp": "gulp", 57 | "watch": "gulp watch", 58 | "ngc": "ngc" 59 | } 60 | } -------------------------------------------------------------------------------- /app/shared/components/chat.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 41 | 42 | 43 |
IDMatch
{{match.Id}} {{match.Host}} vs {{match.Guest}}
15 | {{connection}} 16 |
20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 36 | 37 | 38 |
Message
31 | {{message.CreatedAt | date:'shortTime' }} [{{message.MatchId}}] 32 | 34 | {{message.Text}} 35 |
39 |
40 |
-------------------------------------------------------------------------------- /Controllers/MatchesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.SignalR.Infrastructure; 4 | using LiveGameFeed.Hubs; 5 | using LiveGameFeed.Data.Abstract; 6 | using LiveGameFeed.Models; 7 | using AutoMapper; 8 | 9 | namespace LiveGameFeed.Controllers 10 | { 11 | [Route("api/[controller]")] 12 | public class MatchesController : ApiHubController 13 | { 14 | IMatchRepository _matchRepository; 15 | public MatchesController( 16 | IConnectionManager signalRConnectionManager, 17 | IMatchRepository matchRepository) 18 | : base(signalRConnectionManager) 19 | { 20 | _matchRepository = matchRepository; 21 | } 22 | 23 | // GET api/values 24 | [HttpGet] 25 | public IEnumerable Get() 26 | { 27 | IEnumerable _matches = _matchRepository.AllIncluding(m => m.Feeds); 28 | IEnumerable _matchesVM = Mapper.Map, IEnumerable>(_matches); 29 | 30 | return _matchesVM; 31 | } 32 | 33 | // PUT api/values/5 34 | [HttpPut("{id}")] 35 | public async void Put(int id, [FromBody]MatchScore score) 36 | { 37 | Match _match = _matchRepository.GetSingle(id); 38 | if (score.HostScore == 0 && score.GuestScore == 0) 39 | { 40 | _match.Feeds.Clear(); 41 | } 42 | 43 | _match.HostScore = score.HostScore; 44 | _match.GuestScore = score.GuestScore; 45 | 46 | _matchRepository.Commit(); 47 | 48 | MatchViewModel _matchVM = Mapper.Map(_match); 49 | await Clients.All.UpdateMatch(_matchVM); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #1d3346; 3 | } 4 | 5 | .box { 6 | height: 50px; 7 | /*background-color: #286090;*/ 8 | margin:5px; 9 | padding: 10px; 10 | text-align: center; 11 | } 12 | 13 | .teamScore { 14 | color:orange; 15 | font-weight: bold; 16 | } 17 | 18 | .teamName { 19 | color:ghostwhite; 20 | } 21 | 22 | .match-th { 23 | color:#a94442; 24 | } 25 | 26 | .match-type { 27 | color: #FF5722; 28 | margin-bottom: 0px; 29 | font-size: 22px; 30 | font-style: italic; 31 | } 32 | 33 | th, td { 34 | text-align: center; 35 | } 36 | 37 | .feed-table { 38 | height: 190px; 39 | overflow: auto; 40 | } 41 | 42 | .chat-table { 43 | height: 253px; 44 | overflow: auto; 45 | } 46 | 47 | .feed-time { 48 | color:brown; 49 | font-size: 10px; 50 | } 51 | 52 | .feed-update { 53 | color: #337ab7; 54 | } 55 | 56 | .feed-highlight { 57 | background-color: #a94442; 58 | -webkit-transition: background-color 2000ms linear; 59 | -moz-transition: background-color 2000ms linear; 60 | -o-transition: background-color 2000ms linear; 61 | -ms-transition: background-color 2000ms linear; 62 | transition: background-color 2000ms linear; 63 | } 64 | 65 | .feed-highlight-light { 66 | background-color: white; 67 | -webkit-transition: background-color 2000ms linear; 68 | -moz-transition: background-color 2000ms linear; 69 | -o-transition: background-color 2000ms linear; 70 | -ms-transition: background-color 2000ms linear; 71 | transition: background-color 2000ms linear; 72 | } 73 | 74 | footer { 75 | position: fixed; 76 | height: 40px; 77 | line-height: 40px; 78 | text-align: center; 79 | background-color: #222222; 80 | bottom: 0; 81 | width: 100%; 82 | } 83 | 84 | .orange { 85 | color: #ff5525; 86 | } -------------------------------------------------------------------------------- /app/shared/services/data.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response, Headers } from '@angular/http'; 3 | //Grab everything with import 'rxjs/Rx'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import {Observer} from 'rxjs/Observer'; 6 | import 'rxjs/add/operator/map'; 7 | import 'rxjs/add/operator/catch'; 8 | 9 | import { ChatMessage, Match } from '../interfaces'; 10 | import { ConfigService } from './config.service'; 11 | 12 | @Injectable() 13 | export class DataService { 14 | 15 | _baseUrl: string = ''; 16 | 17 | constructor(private http: Http, 18 | private configService: ConfigService) { 19 | this._baseUrl = configService.getApiURI(); 20 | } 21 | 22 | getMatches(): Observable { 23 | return this.http.get(this._baseUrl + 'matches') 24 | .map(this.extractData) 25 | .catch(this.handleError); 26 | } 27 | 28 | addChatMessage(message: ChatMessage): Observable { 29 | 30 | let headers = new Headers(); 31 | headers.append('Content-Type', 'application/json'); 32 | 33 | return this.http.post(this._baseUrl + 'messages/', JSON.stringify(message), { 34 | headers: headers 35 | }) 36 | .map((res: Response) => { 37 | return null; 38 | }) 39 | .catch(this.handleError); 40 | } 41 | 42 | private extractData(res: Response) { 43 | let body = res.json(); 44 | return body || {}; 45 | } 46 | 47 | private handleError(error: any) { 48 | // In a real world app, we might use a remote logging infrastructure 49 | // We'd also dig deeper into the error to get a better message 50 | let errMsg = (error.message) ? error.message : 51 | error.status ? `${error.status} - ${error.statusText}` : 'Server error'; 52 | console.error(errMsg); // log to console instead 53 | return Observable.throw(errMsg); 54 | } 55 | } -------------------------------------------------------------------------------- /wwwroot/js/systemjs.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * System configuration for Angular 2 samples 3 | * Adjust as necessary for your application needs. 4 | */ 5 | (function (global) { 6 | System.config({ 7 | paths: { 8 | // paths serve as alias 9 | 'npm:': 'lib/' 10 | }, 11 | // map tells the System loader where to look for things 12 | map: { 13 | // our app is within the app folder 14 | app: 'app', 15 | // angular bundles 16 | '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js', 17 | '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js', 18 | '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js', 19 | '@angular/core': 'npm:@angular/core/bundles/core.umd.js', 20 | '@angular/common': 'npm:@angular/common/bundles/common.umd.js', 21 | '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', 22 | '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', 23 | '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', 24 | '@angular/http': 'npm:@angular/http/bundles/http.umd.js', 25 | '@angular/router': 'npm:@angular/router/bundles/router.umd.js', 26 | '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', 27 | // other libraries 28 | 'rxjs': 'npm:rxjs', 29 | 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' 30 | }, 31 | // packages tells the System loader how to load when no filename and/or no extension 32 | packages: { 33 | app: { 34 | main: './main.js', 35 | defaultExtension: 'js' 36 | }, 37 | rxjs: { 38 | defaultExtension: 'js' 39 | }, 40 | 'angular2-in-memory-web-api': { 41 | main: './index.js', 42 | defaultExtension: 'js' 43 | } 44 | } 45 | }); 46 | })(this); -------------------------------------------------------------------------------- /Data/LiveGameDbInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LiveGameFeed.Models; 5 | 6 | namespace LiveGameFeed.Data 7 | { 8 | public class LiveGameDbInitializer 9 | { 10 | private static LiveGameContext context; 11 | public static void Initialize(IServiceProvider serviceProvider) 12 | { 13 | context = (LiveGameContext)serviceProvider.GetService(typeof(LiveGameContext)); 14 | 15 | InitializeSchedules(); 16 | } 17 | 18 | private static void InitializeSchedules() 19 | { 20 | if (!context.Matches.Any()) 21 | { 22 | Match match_01 = new Match 23 | { 24 | Host = "Team 1", 25 | Guest = "Team 2", 26 | HostScore = 0, 27 | GuestScore = 0, 28 | MatchDate = DateTime.Now, 29 | Type = MatchTypeEnums.Basketball, 30 | Feeds = new List 31 | { 32 | new Feed() 33 | { 34 | Description = "Match started", 35 | MatchId = 1, 36 | CreatedAt = DateTime.Now 37 | } 38 | } 39 | }; 40 | 41 | Match match_02 = new Match 42 | { 43 | Host = "Team 3", 44 | Guest = "Team 4", 45 | HostScore = 0, 46 | GuestScore = 0, 47 | MatchDate = DateTime.Now, 48 | Type = MatchTypeEnums.Basketball, 49 | Feeds = new List 50 | { 51 | new Feed() 52 | { 53 | Description = "Match started", 54 | MatchId = 2, 55 | CreatedAt = DateTime.Now 56 | } 57 | } 58 | }; 59 | 60 | context.Matches.Add(match_01); context.Matches.Add(match_02); 61 | 62 | context.SaveChanges(); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /// 2 | "use strict"; 3 | 4 | var gulp = require("gulp"); 5 | var config = require('./gulp.config')(); 6 | var $ = require('gulp-load-plugins')({ lazy: true }); 7 | 8 | gulp.task("clean:js", function (cb) { 9 | return $.rimraf(config.concatJsDest, cb); 10 | }); 11 | 12 | gulp.task("clean:css", function (cb) { 13 | $.rimraf(config.concatCssDest, cb); 14 | }); 15 | 16 | gulp.task("clean", ["clean:js", "clean:css"]); 17 | 18 | gulp.task("copy:angular", function () { 19 | 20 | return gulp.src(config.angular, 21 | { base: config.node_modules + "@angular/" }) 22 | .pipe(gulp.dest(config.lib + "@angular/")); 23 | }); 24 | 25 | gulp.task("copy:angularWebApi", function () { 26 | return gulp.src(config.angularWebApi, 27 | { base: config.node_modules }) 28 | .pipe(gulp.dest(config.lib)); 29 | }); 30 | 31 | gulp.task("copy:corejs", function () { 32 | return gulp.src(config.corejs, 33 | { base: config.node_modules }) 34 | .pipe(gulp.dest(config.lib)); 35 | }); 36 | 37 | gulp.task("copy:zonejs", function () { 38 | return gulp.src(config.zonejs, 39 | { base: config.node_modules }) 40 | .pipe(gulp.dest(config.lib)); 41 | }); 42 | 43 | gulp.task("copy:reflectjs", function () { 44 | return gulp.src(config.reflectjs, 45 | { base: config.node_modules }) 46 | .pipe(gulp.dest(config.lib)); 47 | }); 48 | 49 | gulp.task("copy:systemjs", function () { 50 | return gulp.src(config.systemjs, 51 | { base: config.node_modules }) 52 | .pipe(gulp.dest(config.lib)); 53 | }); 54 | 55 | gulp.task("copy:rxjs", function () { 56 | return gulp.src(config.rxjs, 57 | { base: config.node_modules }) 58 | .pipe(gulp.dest(config.lib)); 59 | }); 60 | 61 | gulp.task("copy:app", function () { 62 | return gulp.src(config.app) 63 | .pipe(gulp.dest(config.appDest)); 64 | }); 65 | 66 | gulp.task("copy:jasmine", function () { 67 | return gulp.src(config.jasminejs, 68 | { base: config.node_modules + "jasmine-core/lib" }) 69 | .pipe(gulp.dest(config.lib)); 70 | }); 71 | 72 | gulp.task("dependencies", ["copy:angular", 73 | "copy:angularWebApi", 74 | "copy:corejs", 75 | "copy:zonejs", 76 | "copy:reflectjs", 77 | "copy:systemjs", 78 | "copy:rxjs", 79 | "copy:jasmine", 80 | "copy:app"]); 81 | 82 | gulp.task("watch", function () { 83 | return $.watch(config.app) 84 | .pipe(gulp.dest(config.appDest)); 85 | }); 86 | 87 | gulp.task("default", ["clean", "dependencies"]); -------------------------------------------------------------------------------- /app/shared/components/match.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 57 | 58 | 59 |
TeamScore
{{match.Host}} {{match.HostScore}}
{{match.Guest}} {{match.GuestScore}}
19 | {{match.Type}} 20 | 22 | 25 | 28 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 52 | 53 | 54 |
Update
47 | {{feed.CreatedAt | date:'shortTime' }} 48 | 50 | {{feed.Description}} 51 |
55 |
56 |
-------------------------------------------------------------------------------- /aspnet-core-signalr-angular.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp1.1 5 | portable 6 | true 7 | aspnet-core-signalr-angular 8 | Exe 9 | aspnet-core-signalr-angular 10 | win10-x64;win81-x64;win8-x64;win7-x64 11 | 1.1.1 12 | $(PackageTargetFallback);dotnet5.6;portable-net45+win8 13 | 14 | 15 | 16 | 17 | PreserveNewest 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Core/FeedEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using LiveGameFeed.Data.Abstract; 5 | using LiveGameFeed.Models; 6 | using Microsoft.Extensions.Logging; 7 | using RecurrentTasks; 8 | 9 | namespace LiveGameFeed.Core 10 | { 11 | public class FeedEngine : IRunnable 12 | { 13 | private ILogger logger; 14 | IMatchRepository _matchRepository; 15 | public FeedEngine(IMatchRepository matchRepository, 16 | ILogger logger) 17 | { 18 | this.logger = logger; 19 | this._matchRepository = matchRepository; 20 | } 21 | public void Run(TaskRunStatus taskRunStatus) 22 | { 23 | var msg = string.Format("Run at: {0}", DateTimeOffset.Now); 24 | logger.LogDebug(msg); 25 | UpdateScore(); 26 | } 27 | 28 | private async void UpdateScore() 29 | { 30 | IEnumerable _matches = _matchRepository.GetAll(); 31 | 32 | foreach (var match in _matches) 33 | { 34 | Random r = new Random(); 35 | bool updateHost = r.Next(0, 2) == 1; 36 | int points = r.Next(2, 4); 37 | bool _matchEnded = false; 38 | 39 | if (updateHost) 40 | match.HostScore += points; 41 | else 42 | match.GuestScore += points; 43 | 44 | MatchScore score = new MatchScore() 45 | { 46 | HostScore = match.HostScore, 47 | GuestScore = match.GuestScore 48 | }; 49 | 50 | if (score.HostScore > 80 || score.GuestScore > 76) 51 | { 52 | score.HostScore = 0; 53 | score.GuestScore = 0; 54 | _matchEnded = true; 55 | } 56 | // Update Score for all clients 57 | using (var client = new HttpClient()) 58 | { 59 | await client.PutAsJsonAsync(Startup.API_URL + "matches/" + match.Id, score); 60 | } 61 | 62 | // Update Feed for subscribed only clients 63 | 64 | 65 | FeedViewModel _feed = new FeedViewModel() 66 | { 67 | MatchId = match.Id, 68 | Description = _matchEnded == false ? 69 | (points + " points for " + (updateHost == true ? match.Host : match.Guest) + "!") : 70 | "Match started", 71 | CreatedAt = DateTime.Now 72 | }; 73 | using (var client = new HttpClient()) 74 | { 75 | await client.PostAsJsonAsync(Startup.API_URL + "feeds", _feed); 76 | } 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LiveGameFeed.Data; 3 | using LiveGameFeed.Data.Repositories; 4 | using LiveGameFeed.Data.Abstract; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using LiveGameFeed.Core.Mappings; 12 | using Newtonsoft.Json.Serialization; 13 | using RecurrentTasks; 14 | using LiveGameFeed.Core; 15 | 16 | namespace LiveGameFeed 17 | { 18 | public class Startup 19 | { 20 | public static string API_URL = string.Empty; 21 | public Startup(IHostingEnvironment env) 22 | { 23 | var builder = new ConfigurationBuilder() 24 | .SetBasePath(env.ContentRootPath) 25 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 26 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 27 | .AddEnvironmentVariables(); 28 | Configuration = builder.Build(); 29 | 30 | API_URL = Configuration["apiURL"]; 31 | } 32 | 33 | public IConfigurationRoot Configuration { get; } 34 | 35 | // This method gets called by the runtime. Use this method to add services to the container. 36 | public void ConfigureServices(IServiceCollection services) 37 | { 38 | services.AddDbContext(options => options.UseInMemoryDatabase()); 39 | // Repositories 40 | services.AddScoped(); 41 | services.AddScoped(); 42 | 43 | // Automapper Configuration 44 | AutoMapperConfiguration.Configure(); 45 | 46 | // Add framework services. 47 | services 48 | .AddMvc() 49 | .AddJsonOptions(options => options.SerializerSettings.ContractResolver = 50 | new DefaultContractResolver()); 51 | 52 | services.AddSignalR(options => options.Hubs.EnableDetailedErrors = true); 53 | 54 | services.AddTask(); 55 | } 56 | 57 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 58 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 59 | { 60 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 61 | loggerFactory.AddDebug(); 62 | 63 | app.UseCors( 64 | builder => builder.AllowAnyOrigin() 65 | .AllowAnyHeader() 66 | .AllowAnyMethod() 67 | .AllowCredentials()) 68 | .UseStaticFiles() 69 | .UseWebSockets(); 70 | 71 | app.UseMvc(routes => 72 | { 73 | routes.MapRoute( 74 | name: "default", 75 | template: "{controller=Home}/{action=Index}/{id?}"); 76 | }); 77 | app.UseSignalR(); 78 | 79 | LiveGameDbInitializer.Initialize(app.ApplicationServices); 80 | 81 | app.StartTask(TimeSpan.FromSeconds(15)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 51 |
52 | Loading... 53 |
54 | 55 | 58 | 59 | 74 | 75 | -------------------------------------------------------------------------------- /Data/Repositories/EntityBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking; 3 | using LiveGameFeed.Data.Abstract; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using LiveGameFeed.Models; 9 | 10 | namespace LiveGameFeed.Data.Repositories 11 | { 12 | public class EntityBaseRepository : IEntityBaseRepository 13 | where T : class, IEntityBase, new() 14 | { 15 | 16 | private LiveGameContext _context; 17 | 18 | #region Properties 19 | public EntityBaseRepository(LiveGameContext context) 20 | { 21 | _context = context; 22 | } 23 | #endregion 24 | public virtual IEnumerable GetAll() 25 | { 26 | return _context.Set().AsEnumerable(); 27 | } 28 | 29 | public virtual int Count() 30 | { 31 | return _context.Set().Count(); 32 | } 33 | public virtual IEnumerable AllIncluding(params Expression>[] includeProperties) 34 | { 35 | IQueryable query = _context.Set(); 36 | foreach (var includeProperty in includeProperties) 37 | { 38 | query = query.Include(includeProperty); 39 | } 40 | return query.AsEnumerable(); 41 | } 42 | 43 | public T GetSingle(int id) 44 | { 45 | return _context.Set().FirstOrDefault(x => x.Id == id); 46 | } 47 | 48 | public T GetSingle(Expression> predicate) 49 | { 50 | return _context.Set().FirstOrDefault(predicate); 51 | } 52 | 53 | public T GetSingle(Expression> predicate, params Expression>[] includeProperties) 54 | { 55 | IQueryable query = _context.Set(); 56 | foreach (var includeProperty in includeProperties) 57 | { 58 | query = query.Include(includeProperty); 59 | } 60 | 61 | return query.Where(predicate).FirstOrDefault(); 62 | } 63 | 64 | public virtual IEnumerable FindBy(Expression> predicate) 65 | { 66 | return _context.Set().Where(predicate); 67 | } 68 | 69 | public virtual void Add(T entity) 70 | { 71 | EntityEntry dbEntityEntry = _context.Entry(entity); 72 | _context.Set().Add(entity); 73 | } 74 | 75 | public virtual void Update(T entity) 76 | { 77 | EntityEntry dbEntityEntry = _context.Entry(entity); 78 | dbEntityEntry.State = EntityState.Modified; 79 | } 80 | public virtual void Delete(T entity) 81 | { 82 | EntityEntry dbEntityEntry = _context.Entry(entity); 83 | dbEntityEntry.State = EntityState.Deleted; 84 | } 85 | 86 | public virtual void DeleteWhere(Expression> predicate) 87 | { 88 | IEnumerable entities = _context.Set().Where(predicate); 89 | 90 | foreach(var entity in entities) 91 | { 92 | _context.Entry(entity).State = EntityState.Deleted; 93 | } 94 | } 95 | 96 | public virtual void Commit() 97 | { 98 | _context.SaveChanges(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/shared/services/feed.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/toPromise'; 5 | import { Observable } from "rxjs/Observable"; 6 | import { Subject } from "rxjs/Subject"; 7 | 8 | import { FeedSignalR, FeedProxy, FeedClient, FeedServer, SignalRConnectionStatus, ChatMessage, Match, Feed } from '../interfaces'; 9 | 10 | @Injectable() 11 | export class FeedService { 12 | 13 | currentState = SignalRConnectionStatus.Disconnected; 14 | connectionState: Observable; 15 | 16 | setConnectionId: Observable; 17 | updateMatch: Observable; 18 | addFeed: Observable; 19 | addChatMessage: Observable; 20 | 21 | private connectionStateSubject = new Subject(); 22 | 23 | private setConnectionIdSubject = new Subject(); 24 | private updateMatchSubject = new Subject(); 25 | private addFeedSubject = new Subject(); 26 | private addChatMessageSubject = new Subject(); 27 | 28 | private server: FeedServer; 29 | 30 | constructor(private http: Http) { 31 | this.connectionState = this.connectionStateSubject.asObservable(); 32 | 33 | this.setConnectionId = this.setConnectionIdSubject.asObservable(); 34 | this.updateMatch = this.updateMatchSubject.asObservable(); 35 | this.addFeed = this.addFeedSubject.asObservable(); 36 | this.addChatMessage = this.addChatMessageSubject.asObservable(); 37 | } 38 | 39 | start(debug: boolean): Observable { 40 | 41 | $.connection.hub.logging = debug; 42 | 43 | let connection = $.connection; 44 | // reference signalR hub named 'broadcaster' 45 | let feedHub = connection.broadcaster; 46 | this.server = feedHub.server; 47 | 48 | // setConnectionId method called by server 49 | feedHub.client.setConnectionId = id => this.onSetConnectionId(id); 50 | 51 | // updateMatch method called by server 52 | feedHub.client.updateMatch = match => this.onUpdateMatch(match); 53 | 54 | // addFeed method called by server 55 | feedHub.client.addFeed = feed => this.onAddFeed(feed); 56 | 57 | feedHub.client.addChatMessage = chatMessage => this.onAddChatMessage(chatMessage); 58 | 59 | // start the connection 60 | $.connection.hub.start() 61 | .done(response => this.setConnectionState(SignalRConnectionStatus.Connected)) 62 | .fail(error => this.connectionStateSubject.error(error)); 63 | 64 | return this.connectionState; 65 | } 66 | 67 | private setConnectionState(connectionState: SignalRConnectionStatus) { 68 | console.log('connection state changed to: ' + connectionState); 69 | this.currentState = connectionState; 70 | this.connectionStateSubject.next(connectionState); 71 | } 72 | 73 | // Client side methods 74 | private onSetConnectionId(id: string) { 75 | this.setConnectionIdSubject.next(id); 76 | } 77 | 78 | private onUpdateMatch(match: Match) { 79 | this.updateMatchSubject.next(match); 80 | } 81 | 82 | private onAddFeed(feed: Feed) { 83 | console.log(feed); 84 | this.addFeedSubject.next(feed); 85 | } 86 | 87 | private onAddChatMessage(chatMessage: ChatMessage) { 88 | this.addChatMessageSubject.next(chatMessage); 89 | } 90 | 91 | // Server side methods 92 | public subscribeToFeed(matchId: number) { 93 | this.server.subscribe(matchId); 94 | } 95 | 96 | public unsubscribeFromFeed(matchId: number) { 97 | this.server.unsubscribe(matchId); 98 | } 99 | } -------------------------------------------------------------------------------- /app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { FeedService } from '../shared/services/feed.service'; 4 | import { Match, Feed } from '../shared/interfaces'; 5 | import { DataService } from '../shared/services/data.service'; 6 | import { SignalRConnectionStatus } from '../shared/interfaces'; 7 | 8 | @Component({ 9 | selector: 'home', 10 | templateUrl: 'app/home/home.component.html' 11 | }) 12 | export class HomeComponent implements OnInit { 13 | 14 | matches: Match[]; 15 | connectionId: string; 16 | error: any; 17 | 18 | constructor(private dataService: DataService, 19 | private feedService: FeedService) { } 20 | 21 | ngOnInit() { 22 | let self = this; 23 | 24 | self.listenForConnection(); 25 | 26 | self.feedService.connectionState 27 | .subscribe( 28 | connectionState => { 29 | if (connectionState == SignalRConnectionStatus.Connected) { 30 | console.log('Connected!'); 31 | self.loadMatches(); 32 | } else { 33 | console.log(connectionState.toString()); 34 | } 35 | }, 36 | error => { 37 | this.error = error; 38 | console.log(error); 39 | }); 40 | } 41 | 42 | loadMatches(): void { 43 | let self = this; 44 | this.dataService.getMatches() 45 | .subscribe((res: Match[]) => { 46 | self.matches = res; 47 | // Listen for match score updates... 48 | self.feedService.updateMatch.subscribe( 49 | match => { 50 | for (var i = 0; i < self.matches.length; i++) { 51 | if (self.matches[i].Id === match.Id) { 52 | self.matches[i].HostScore = match.HostScore; 53 | self.matches[i].GuestScore = match.GuestScore; 54 | 55 | if(match.HostScore === 0 && match.GuestScore === 0) 56 | self.matches[i].Feeds = new Array(); 57 | } 58 | } 59 | } 60 | ); 61 | 62 | // Listen for subscribed feed updates.. 63 | self.feedService.addFeed.subscribe( 64 | feed => { 65 | console.log(feed); 66 | for (var i = 0; i < self.matches.length; i++) { 67 | if (self.matches[i].Id === feed.MatchId) { 68 | if (!self.matches[i].Feeds) { 69 | self.matches[i].Feeds = new Array(); 70 | } 71 | self.matches[i].Feeds.unshift(feed); 72 | } 73 | } 74 | } 75 | ); 76 | }, 77 | error => { 78 | console.log(error); 79 | }); 80 | } 81 | 82 | listenForConnection() { 83 | let self = this; 84 | // Listen for connected / disconnected events 85 | self.feedService.setConnectionId.subscribe( 86 | id => { 87 | console.log(id); 88 | self.connectionId = id; 89 | } 90 | ); 91 | } 92 | 93 | updateSubscription(subscription: any) { 94 | if (subscription.subscribe === true) 95 | this.feedService.subscribeToFeed(subscription.matchId); 96 | else 97 | this.feedService.unsubscribeFromFeed(subscription.matchId); 98 | } 99 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Microsoft Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Microsoft Azure Emulator 160 | ecf/ 161 | rcf/ 162 | 163 | # Microsoft Azure ApplicationInsights config file 164 | ApplicationInsights.config 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | BundleArtifacts/ 169 | 170 | # Visual Studio cache files 171 | # files ending in .cache can be ignored 172 | *.[Cc]ache 173 | # but keep track of directories ending in .cache 174 | !*.[Cc]ache/ 175 | 176 | # Others 177 | ClientBin/ 178 | ~$* 179 | *~ 180 | *.dbmdl 181 | *.dbproj.schemaview 182 | *.pfx 183 | *.publishsettings 184 | node_modules/ 185 | typings/ 186 | wwwroot/lib/ 187 | app/**/*.js 188 | app/**/*.map 189 | wwwroot/app/**/*.* 190 | 191 | orleans.codegen.cs 192 | 193 | # RIA/Silverlight projects 194 | Generated_Code/ 195 | 196 | # Backup & report files from converting an old project file 197 | # to a newer Visual Studio version. Backup files are not needed, 198 | # because we have git ;-) 199 | _UpgradeReport_Files/ 200 | Backup*/ 201 | UpgradeLog*.XML 202 | UpgradeLog*.htm 203 | 204 | # SQL Server files 205 | *.mdf 206 | *.ldf 207 | 208 | # Business Intelligence projects 209 | *.rdl.data 210 | *.bim.layout 211 | *.bim_*.settings 212 | 213 | # Microsoft Fakes 214 | FakesAssemblies/ 215 | 216 | # GhostDoc plugin setting file 217 | *.GhostDoc.xml 218 | 219 | # Node.js Tools for Visual Studio 220 | .ntvs_analysis.dat 221 | 222 | # Visual Studio 6 build log 223 | *.plg 224 | 225 | # Visual Studio 6 workspace options file 226 | *.opt 227 | 228 | # Visual Studio LightSwitch build output 229 | **/*.HTMLClient/GeneratedArtifacts 230 | **/*.DesktopClient/GeneratedArtifacts 231 | **/*.DesktopClient/ModelManifest.xml 232 | **/*.Server/GeneratedArtifacts 233 | **/*.Server/ModelManifest.xml 234 | _Pvt_Extensions 235 | 236 | # Paket dependency manager 237 | .paket/paket.exe 238 | 239 | # FAKE - F# Make 240 | .fake/ 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Real-time applications using ASP.NET Core, SignalR & Angular 2 | [![Build status](https://ci.appveyor.com/api/projects/status/github/chsakell/aspnet-core-signalr-angular?branch=master&svg=true)](https://ci.appveyor.com/project/chsakell/aspnet-core-signalr-angular/branch/master) 3 | 4 | Blog post: Real-time applications using ASP.NET Core, SignalR & Angular
5 |
Features explained
6 |
    7 |
  • Fire up an empty ASP.NET Core web application using yeoman
  • 8 |
  • Configure and install MVC and SignalR Server dependencies
  • 9 |
  • Install SignalR Client-Typescript dependencies
  • 10 |
  • Create SignalR hubs
  • 11 |
  • Integrate MVC Controllers (API) with SignalR
  • 12 |
  • Create the Angular-SignalR service to communicate with SignalR hubs
  • 13 |
  • Add Reccurent Tasks on a ASP.NET Core application
  • 14 |
15 | dotnet-core-api-14 16 | 17 |

Installation Instructions

18 |
    19 |
  1. Clone or download the repository and open it on your favorite editor
  2. 20 |
  3. Make sure you have the latest .NET Core version installed (link 1 - link 2)
  4. 21 |
  5. Open a command prompt and run the following commands 22 |
      23 |
    1. npm install
    2. 24 |
    3. bower install
    4. 25 |
    5. npm start
    6. 26 |
    27 |
  6. 28 |
  7. Open another command prompt and run the following commands 29 |
      30 |
    1. dotnet restore
    2. 31 |
    3. dotnet run
    4. 32 |
    33 |
  8. 34 |
35 | 36 |

37 | 38 | aspnet-core-signalr-angular 39 | 40 |

41 | 42 |

Microsoft Azure Deployment

43 | Learn how to deploy an ASP.NET Core - Angular 2 - SignalR app on Microsoft Azure here. 44 |

Donations

45 | For being part of open source projects and documenting my work here and on chsakell's blog I really do not charge anything. I try to avoid any type of ads also. 46 | 47 | If you think that any information you obtained here is worth of some money and are willing to pay for it, feel free to send any amount through paypal. 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 |
Paypal
54 | Buy me a beer 55 |
59 | 60 |

Follow chsakell's Blog

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | 81 | 82 | 83 |
FacebookTwitter
Microsoft Web Application Development
76 | facebook 77 | 79 | twitter-small 80 |
84 |

License

85 | Code released under the MIT license. 86 | --------------------------------------------------------------------------------