├── .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 | ID
5 | Match
6 |
7 |
8 |
9 |
10 | {{match.Id}}
11 | {{match.Host}} vs {{match.Guest}}
12 |
13 |
14 |
15 | {{connection}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Message
26 |
27 |
28 |
29 |
30 |
31 | {{message.CreatedAt | date:'shortTime' }} [{{message.MatchId}}]
32 |
33 |
34 | {{message.Text}}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
29 |
50 |
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 | [](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 |
16 |
17 | Installation Instructions
18 |
19 | Clone or download the repository and open it on your favorite editor
20 | Make sure you have the latest .NET Core version installed (link 1 - link 2 )
21 | Open a command prompt and run the following commands
22 |
23 | npm install
24 | bower install
25 | npm start
26 |
27 |
28 | Open another command prompt and run the following commands
29 |
30 | dotnet restore
31 | dotnet run
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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 | Paypal
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Follow chsakell's Blog
61 |
62 |
63 |
64 | Facebook
65 | Twitter
66 |
67 |
68 |
69 |
70 | Microsoft Web Application Development
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | License
85 | Code released under the MIT license .
86 |
--------------------------------------------------------------------------------