├── .gitignore ├── README.md └── src ├── .babelrc ├── ClientApp ├── .editorconfig ├── boot-client.js ├── boot-server.js ├── components │ ├── counter.jsx │ ├── fetchData.jsx │ ├── home.jsx │ ├── layout.jsx │ └── navMenu.jsx ├── configureStore.js ├── css │ └── site.css ├── reducers │ ├── counter.js │ ├── index.js │ └── weatherForecasts.js └── routes.js ├── Controllers ├── HomeController.cs └── SampleDataController.cs ├── Properties └── launchSettings.json ├── ReactReduxSpaES6.xproj ├── Startup.cs ├── Views ├── Home │ └── Index.cshtml ├── Shared │ ├── Error.cshtml │ └── _Layout.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── appsettings.json ├── jsconfig.json ├── package.json ├── project.json ├── webpack.config.js ├── webpack.config.prod.js └── wwwroot └── favicon.ico /.gitignore: -------------------------------------------------------------------------------- 1 | /Properties/launchSettings.json 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | build/ 23 | bld/ 24 | bin/ 25 | Bin/ 26 | obj/ 27 | Obj/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | /wwwroot/dist/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | *.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | 158 | # Microsoft Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Microsoft Azure Emulator 163 | ecf/ 164 | rcf/ 165 | 166 | # Microsoft Azure ApplicationInsights config file 167 | ApplicationInsights.config 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | ~$* 182 | *~ 183 | *.dbmdl 184 | *.dbproj.schemaview 185 | *.pfx 186 | *.publishsettings 187 | node_modules/ 188 | orleans.codegen.cs 189 | 190 | # RIA/Silverlight projects 191 | Generated_Code/ 192 | 193 | # Backup & report files from converting an old project file 194 | # to a newer Visual Studio version. Backup files are not needed, 195 | # because we have git ;-) 196 | _UpgradeReport_Files/ 197 | Backup*/ 198 | UpgradeLog*.XML 199 | UpgradeLog*.htm 200 | 201 | # SQL Server files 202 | *.mdf 203 | *.ldf 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # Paket dependency manager 234 | .paket/paket.exe 235 | 236 | # FAKE - F# Make 237 | .fake/ 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## What is this? 3 | 4 | It's a template project for a SPA application on [asp.net core](https://www.microsoft.com/net) using [React.js](https://facebook.github.io/react/) and [Redux](http://redux.js.org/) written in ES2015 syntax. It's the same template as the one in [original repository](https://github.com/aspnet/JavaScriptServices) that is called [ReacReduxSpa](https://github.com/aspnet/JavaScriptServices/tree/master/templates/ReactReduxSpa) but it's developed in JavaScript rather than TypeScript. 5 | 6 | ## What is JavaScript services? 7 | 8 | `JavaScriptServices` is a set of technologies for ASP.NET Core developers. It provides infrastructure that you'll find useful if you use Angular 2 / React / Knockout / etc. on the client, or if you build your client-side resources using Webpack, or otherwise want to execute JavaScript on the server at runtime. Everything is cross-platform, and works with .NET Core 1.0 RC2 or later on Windows, Linux, or OS X. 9 | 10 | ## Differences with the TypeScript version 11 | There are small differences with the [corresponding](https://github.com/aspnet/JavaScriptServices/tree/master/templates/ReactReduxSpa) TypeScript version. Most importantly there is no yo generator for this template so far. So if you want to create an application based on this template you have to clone it locally and modify it according to your needs. 12 | 13 | On top of that there is a slightly difference in the way the bundles are created during development and production. Initially there is no need to create explicitly the vendor bundle by executing the webpack command before launching the project. It's enough to just execute `dotnet run` and the library will take care creating the bundles. For production now you have to manually create the bundles and probably add them to source control as they are part of your application. In order to create these bundles you have to execute `npm run dist` from the root of the app. This could be also part of your build process. Once it's finished successfully there will be two JavaScript files one containing the code of yout application and the other one all the vendor code that doesn't change often, only when upgrading or adding new libraries. The generated files are located inside the `wwwroot\dist` folder. 14 | 15 | **For more information please visit the [JavaScriptServices project](https://github.com/aspnet/JavaScriptServices)** -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /src/ClientApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | -------------------------------------------------------------------------------- /src/ClientApp/boot-client.js: -------------------------------------------------------------------------------- 1 | import './css/site.css'; 2 | import 'jquery'; 3 | import 'bootstrap'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import { Provider } from 'react-redux'; 7 | import { browserHistory, Router } from 'react-router'; 8 | import { syncHistoryWithStore } from 'react-router-redux'; 9 | import configureStore from './configureStore'; 10 | import routes from './routes'; 11 | 12 | // Get the application-wide store instance, prepopulating with state from the server where available. 13 | const initialState = window.initialReduxState; 14 | const store = configureStore(initialState); 15 | const history = syncHistoryWithStore(browserHistory, store); 16 | 17 | // This code starts up the React app when it runs in a browser. It sets up the routing configuration 18 | // and injects the app into a DOM element. 19 | ReactDOM.render( 20 | 21 | 22 | , 23 | document.getElementById('react-app') 24 | ); 25 | -------------------------------------------------------------------------------- /src/ClientApp/boot-server.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderToString } from 'react-dom/server'; 3 | import { Provider } from 'react-redux'; 4 | import { match, RouterContext } from 'react-router'; 5 | 6 | import routes from './routes'; 7 | import configureStore from './configureStore'; 8 | 9 | export default (params) => { 10 | return new Promise((resolve, reject) => { 11 | match({routes, location: params.location}, (error, redirectLocation, renderProps) => { 12 | if (error) { 13 | throw error; 14 | } 15 | 16 | // If there's a redirection, just send this information back to the host application 17 | if (redirectLocation) { 18 | resolve({ redirectUrl: redirectLocation.pathname }); 19 | } 20 | 21 | // If it didn't match any route, renderProps will be undefined 22 | if (!renderProps) { 23 | throw new Error(`The location '${ params.url }' doesn't match any route configured in react-router.`); 24 | } 25 | 26 | // At this point if we want to initialize the store we need to pass an object with shape 27 | // {counter: {count: 10}} 28 | const store = configureStore(); 29 | const app = ( 30 | 31 | 32 | 33 | ); 34 | 35 | // Perform an initial render that will cause any async tasks (e.g., data access) to begin 36 | renderToString(app); 37 | 38 | // Once the tasks are done, we can perform the final render 39 | // We also send the redux store state, so the client can continue execution where the server left off 40 | params.domainTasks.then(() => { 41 | resolve({ 42 | html: renderToString(app), 43 | globals: { initialReduxState: store.getState() } 44 | }); 45 | }, reject); // Also propagate any errors back into the host application 46 | }); 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /src/ClientApp/components/counter.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { incrementCount } from '../reducers/counter'; 4 | 5 | class Counter extends Component { 6 | render() { 7 | return ( 8 |
9 |

Counter

10 |

This is a simple example of a React component.

11 |

Current count: { this.props.count }

12 | 13 |
14 | ); 15 | } 16 | } 17 | 18 | Counter.propTypes = { 19 | count: PropTypes.number.isRequired, 20 | increment: PropTypes.func.isRequired, 21 | }; 22 | 23 | const mapStateToProps = (state) => ({ 24 | count: state.counter.count 25 | }); 26 | 27 | const mapDispatchToProps = (dispatch) => ({ 28 | increment: () => { 29 | dispatch(incrementCount()); 30 | } 31 | }); 32 | 33 | export default connect( 34 | mapStateToProps, 35 | mapDispatchToProps 36 | )(Counter); 37 | -------------------------------------------------------------------------------- /src/ClientApp/components/fetchData.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router'; 4 | import { requestWeatherForecasts } from '../reducers/weatherForecasts' 5 | 6 | class FetchData extends Component { 7 | componentWillMount() { 8 | // This method runs when the component is first added to the page 9 | let startDateIndex = parseInt(this.props.params.startDateIndex) || 0; 10 | this.props.requestWeatherForecasts(startDateIndex); 11 | } 12 | 13 | componentWillReceiveProps(nextProps) { 14 | // This method runs when incoming props (e.g., route params) change 15 | let startDateIndex = parseInt(nextProps.params.startDateIndex) || 0; 16 | this.props.requestWeatherForecasts(startDateIndex); 17 | } 18 | 19 | render() { 20 | const table = ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {this.props.forecasts.map(forecast => 32 | 33 | 34 | 35 | 36 | 37 | 38 | )} 39 | 40 |
DateTemp. (C)Temp. (F)Summary
{ forecast.dateFormatted }{ forecast.temperatureC }{ forecast.temperatureF }{ forecast.summary }
41 | ); 42 | 43 | let prevStartDateIndex = this.props.startDateIndex - 5; 44 | let nextStartDateIndex = this.props.startDateIndex + 5; 45 | const pagination = ( 46 |

47 | Previous 48 | Next 49 | { this.props.isLoading ? Loading... : [] } 50 |

51 | ); 52 | 53 | return ( 54 |
55 |

Weather forecast

56 |

This component demonstrates fetching data from the server and working with URL parameters.

57 | { table } 58 | { pagination } 59 |
60 | ); 61 | } 62 | } 63 | 64 | FetchData.propTypes = { 65 | startDateIndex: PropTypes.number, 66 | isLoading: PropTypes.bool, 67 | requestWeatherForecasts: PropTypes.func.isRequired 68 | }; 69 | 70 | const mapStateToProps = (state) => ({ 71 | startDateIndex: state.weatherForecasts.startDateIndex, 72 | forecasts: state.weatherForecasts.forecasts, 73 | isLoading: state.weatherForecasts.isLoading 74 | }); 75 | 76 | const mapDispatchToProps = (dispatch) => ({ 77 | requestWeatherForecasts: (startDateIndex) => { 78 | dispatch(requestWeatherForecasts(startDateIndex)); 79 | } 80 | }); 81 | 82 | export default connect( 83 | mapStateToProps, 84 | mapDispatchToProps 85 | )(FetchData); 86 | -------------------------------------------------------------------------------- /src/ClientApp/components/home.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | class Home extends Component { 4 | render() { 5 | return ( 6 |
7 |

Hello, world!

8 |

Welcome to your new single-page application, built with:

9 | 15 |

To help you get started, we've also set up:

16 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | export default Home; 29 | -------------------------------------------------------------------------------- /src/ClientApp/components/layout.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import NavMenu from './NavMenu'; 3 | 4 | class Layout extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 |
13 | {this.props.body} 14 |
15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | Layout.propTypes = { 22 | body: PropTypes.element, 23 | }; 24 | 25 | export default Layout; 26 | -------------------------------------------------------------------------------- /src/ClientApp/components/navMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | class NavMenu extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |
10 | 16 | WebApplicationBasic 17 |
18 |
19 |
20 |
    21 |
  • 22 | 23 | Home 24 | 25 |
  • 26 |
  • 27 | 28 | Counter 29 | 30 |
  • 31 |
  • 32 | 33 | Fetch data 34 | 35 |
  • 36 |
37 |
38 |
39 |
40 | ); 41 | } 42 | } 43 | 44 | export default NavMenu; 45 | -------------------------------------------------------------------------------- /src/ClientApp/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import rootReducer from './reducers' 4 | 5 | export default (initialState) => { 6 | const windowIfDefined = typeof window === 'undefined' ? null : window; 7 | const devToolsExtension = windowIfDefined && windowIfDefined.devToolsExtension; // If devTools is installed, connect to it 8 | const createStoreWithMiddleware = compose( 9 | applyMiddleware(thunkMiddleware), 10 | devToolsExtension ? devToolsExtension() : f => f 11 | )(createStore); 12 | 13 | const store = createStoreWithMiddleware(rootReducer, initialState); 14 | // Enable Webpack hot module replacement for reducers 15 | if (module.hot) { 16 | module.hot.accept('./reducers', () => { 17 | const nextReducer = require('./reducers').default 18 | store.replaceReducer(nextReducer); 19 | }); 20 | } 21 | 22 | return store; 23 | }; 24 | -------------------------------------------------------------------------------- /src/ClientApp/css/site.css: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/dist/css/bootstrap.css"; 2 | 3 | .main-nav li .glyphicon { 4 | margin-right: 10px; 5 | } 6 | 7 | /* Highlighting rules for nav menu items */ 8 | .main-nav li a.active, 9 | .main-nav li a.active:hover, 10 | .main-nav li a.active:focus { 11 | background-color: #4189C7; 12 | color: white; 13 | } 14 | 15 | /* Keep the nav menu independent of scrolling and on top of other items */ 16 | .main-nav { 17 | position: fixed; 18 | top: 0; 19 | left: 0; 20 | right: 0; 21 | z-index: 1; 22 | } 23 | 24 | @media (max-width: 767px) { 25 | /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ 26 | body { 27 | padding-top: 50px; 28 | } 29 | } 30 | 31 | @media (min-width: 768px) { 32 | /* On small screens, convert the nav menu to a vertical sidebar */ 33 | .main-nav { 34 | height: 100%; 35 | width: calc(25% - 20px); 36 | } 37 | .main-nav .navbar { 38 | border-radius: 0px; 39 | border-width: 0px; 40 | height: 100%; 41 | } 42 | .main-nav .navbar-header { 43 | float: none; 44 | } 45 | .main-nav .navbar-collapse { 46 | border-top: 1px solid #444; 47 | padding: 0px; 48 | } 49 | .main-nav .navbar ul { 50 | float: none; 51 | } 52 | .main-nav .navbar li { 53 | float: none; 54 | font-size: 15px; 55 | margin: 6px; 56 | } 57 | .main-nav .navbar li a { 58 | padding: 10px 16px; 59 | border-radius: 4px; 60 | } 61 | .main-nav .navbar a { 62 | /* If a menu item's text is too long, truncate it */ 63 | width: 100%; 64 | white-space: nowrap; 65 | overflow: hidden; 66 | text-overflow: ellipsis; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ClientApp/reducers/counter.js: -------------------------------------------------------------------------------- 1 | // Constant defining the action type 2 | const INCREMENT_COUNT = 'INCREMENT_COUNT'; 3 | 4 | // Action creator 5 | export const incrementCount = () => ({type: INCREMENT_COUNT}); 6 | 7 | const initialState = { 8 | count: 0 9 | }; 10 | 11 | // The reducer that changes the state based on the action type 12 | export default (state = initialState, action) => { 13 | if (action.type === INCREMENT_COUNT) { 14 | return { count: state.count + 1 }; 15 | } 16 | 17 | return state; 18 | } 19 | -------------------------------------------------------------------------------- /src/ClientApp/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux' 3 | 4 | import counter from './counter'; 5 | import weatherForecasts from './weatherForecasts'; 6 | 7 | export default combineReducers({ 8 | routing: routerReducer, 9 | counter, 10 | weatherForecasts 11 | }); 12 | -------------------------------------------------------------------------------- /src/ClientApp/reducers/weatherForecasts.js: -------------------------------------------------------------------------------- 1 | import { fetch } from 'domain-task/fetch'; 2 | 3 | // Constant defining the action type 4 | const REQUEST_WEATHER_FORECASTS = 'REQUEST_WEATHER_FORECASTS'; 5 | const RECEIVE_WEATHER_FORECASTS = 'RECEIVE_WEATHER_FORECASTS'; 6 | 7 | // Action creators 8 | export const requestWeatherForecasts = (startDateIndex) => { 9 | return (dispatch, getState) => { 10 | if (startDateIndex !== getState().weatherForecasts.startDateIndex) { 11 | dispatch({type: REQUEST_WEATHER_FORECASTS, payload: startDateIndex}); 12 | return fetch(`/api/SampleData/WeatherForecasts?startDateIndex=${ startDateIndex }`) 13 | .then(response => response.json()) 14 | .then((data) => { 15 | dispatch(receiveWeatherForecasts(startDateIndex, data)); 16 | }); 17 | } 18 | }; 19 | }; 20 | 21 | export const receiveWeatherForecasts = (startDateIndex, forecasts) => ({ 22 | type: RECEIVE_WEATHER_FORECASTS, 23 | payload: {startDateIndex, forecasts}, 24 | }); 25 | 26 | const initialState = { 27 | startDateIndex: null, 28 | forecasts: [], 29 | isLoading: false 30 | }; 31 | 32 | // The reducer that changes the state based on the action type 33 | export default (state = initialState, action) => { 34 | 35 | if (action.type === REQUEST_WEATHER_FORECASTS) { 36 | return { startDateIndex: action.payload, isLoading: true, forecasts: state.forecasts }; 37 | } else if (action.type === RECEIVE_WEATHER_FORECASTS) { 38 | // Only accept the incoming data if it matches the most recent request. This ensures we correctly 39 | // handle out-of-order responses. 40 | if (action.payload.startDateIndex === state.startDateIndex) { 41 | return { startDateIndex: action.payload.startDateIndex, forecasts: action.payload.forecasts, isLoading: false }; 42 | } 43 | } 44 | 45 | return state; 46 | } 47 | -------------------------------------------------------------------------------- /src/ClientApp/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router'; 3 | import Layout from './components/layout'; 4 | import Home from './components/home'; 5 | import FetchData from './components/fetchData'; 6 | import Counter from './components/counter'; 7 | 8 | export default 9 | 10 | 11 | 12 | 13 | { /* Optional route segment that does not affect NavMenu highlighting */ } 14 | 15 | ; 16 | -------------------------------------------------------------------------------- /src/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace ReactReduxSpaES6.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public IActionResult Index() 12 | { 13 | return View(); 14 | } 15 | 16 | public IActionResult Error() 17 | { 18 | return View(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Controllers/SampleDataController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace WebApplicationBasic.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class SampleDataController : Controller 11 | { 12 | private static string[] Summaries = new[] 13 | { 14 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 15 | }; 16 | 17 | [HttpGet("[action]")] 18 | public IEnumerable WeatherForecasts(int startDateIndex) 19 | { 20 | var rng = new Random(); 21 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 22 | { 23 | DateFormatted = DateTime.Now.AddDays(index + startDateIndex).ToString("d"), 24 | TemperatureC = rng.Next(-20, 55), 25 | Summary = Summaries[rng.Next(Summaries.Length)] 26 | }); 27 | } 28 | 29 | public class WeatherForecast 30 | { 31 | public string DateFormatted { get; set; } 32 | public int TemperatureC { get; set; } 33 | public string Summary { get; set; } 34 | 35 | public int TemperatureF 36 | { 37 | get 38 | { 39 | return 32 + (int)(this.TemperatureC / 0.5556); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:54898/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/ReactReduxSpaES6.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 26bfa8ee-1e46-4870-90a3-9e946c569284 10 | ReactReduxSpaES6 11 | .\obj 12 | .\bin\ 13 | v4.6.1 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Newtonsoft.Json.Serialization; 11 | using System.IO; 12 | using Microsoft.AspNetCore.SpaServices.Webpack; 13 | 14 | namespace ReactReduxSpaES6 15 | { 16 | public class Startup 17 | { 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | public void ConfigureServices(IServiceCollection services) 20 | { 21 | // Add framework services. 22 | services.AddMvc().AddJsonOptions(options => { 23 | options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 24 | }); 25 | } 26 | 27 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 28 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 29 | { 30 | app.UseDeveloperExceptionPage(); 31 | if (env.IsDevelopment()) 32 | { 33 | app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { 34 | HotModuleReplacement = true, 35 | ReactHotModuleReplacement = true 36 | }); 37 | } 38 | 39 | app.UseStaticFiles(); 40 | loggerFactory.AddConsole(); 41 | app.UseMvc(routes => 42 | { 43 | routes.MapRoute( 44 | name: "default", 45 | template: "{controller=Home}/{action=Index}/{id?}"); 46 | routes.MapSpaFallbackRoute( 47 | name: "spa-fallback", 48 | defaults: new { controller = "Home", action = "Index" }); 49 | }); 50 | } 51 | 52 | public static void Main(string[] args) { 53 | var host = new WebHostBuilder() 54 | .UseContentRoot(Directory.GetCurrentDirectory()) 55 | .UseIISIntegration() 56 | .UseKestrel() 57 | .UseStartup() 58 | .Build(); 59 | 60 | host.Run(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 |
Loading...
6 | 7 | @section scripts { 8 | 9 | 10 | 11 | 12 | 13 | 14 | } -------------------------------------------------------------------------------- /src/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | -------------------------------------------------------------------------------- /src/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - WebApplicationBasic 7 | 8 | 9 | 10 | 11 | 12 | @RenderBody() 13 | 14 | 15 | 16 | 17 | @RenderSection("scripts", required: false) 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using ReactReduxSpaES6 2 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 3 | @addTagHelper "*, Microsoft.AspNetCore.SpaServices" 4 | -------------------------------------------------------------------------------- /src/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "es6", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dist": "cross-env NODE_ENV=production webpack --bail --progress --colors --config webpack.config.prod.js" 6 | }, 7 | "dependencies": { 8 | "domain-task": "^2.0.1", 9 | "react": "^15.4.1", 10 | "react-dom": "^15.4.1", 11 | "react-redux": "^4.4.6", 12 | "react-router": "^2.8.1", 13 | "react-router-redux": "^4.0.7", 14 | "redux": "^3.6.0", 15 | "redux-thunk": "^2.0.1" 16 | }, 17 | "devDependencies": { 18 | "aspnet-prerendering": "^1.0.7", 19 | "aspnet-webpack": "^1.0.24", 20 | "aspnet-webpack-react": "^1.0.2", 21 | "babel-core": "^6.18.2", 22 | "babel-loader": "^6.2.8", 23 | "babel-preset-es2015": "^6.18.0", 24 | "babel-preset-react": "^6.16.0", 25 | "bootstrap": "^3.3.6", 26 | "cross-env": "^3.1.3", 27 | "css-loader": "^0.26.0", 28 | "extendify": "^1.0.0", 29 | "extract-text-webpack-plugin": "^1.0.1", 30 | "file-loader": "^0.9.0", 31 | "jquery": "^2.2.1", 32 | "style-loader": "^0.13.0", 33 | "url-loader": "^0.5.7", 34 | "webpack": "^1.13.3", 35 | "webpack-hot-middleware": "^2.13.2", 36 | "webpack-node-externals": "^1.5.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true, 5 | "preserveCompilationContext": true 6 | }, 7 | "runtimeOptions": { 8 | "configProperties": { 9 | "System.GC.Server": true 10 | } 11 | }, 12 | 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "version": "1.0.0", 16 | "type": "platform" 17 | }, 18 | "Microsoft.AspNetCore.Diagnostics": "1.0.0", 19 | "Microsoft.AspNetCore.Mvc": "1.0.0", 20 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 21 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 22 | "Microsoft.AspNetCore.StaticFiles": "1.0.0", 23 | "Microsoft.Extensions.Configuration.Json": "1.0.0", 24 | "Microsoft.Extensions.Logging.Console": "1.0.0", 25 | "Microsoft.NETCore.Platforms": "1.0.1", 26 | "Microsoft.Extensions.Logging.Debug": "1.0.0", 27 | "Microsoft.AspNetCore.ReactServices": "1.0.0-*" 28 | }, 29 | "frameworks": { 30 | "netcoreapp1.0": { 31 | "imports": [ 32 | "dotnet5.6", 33 | "dnxcore50", 34 | "portable-net45+win8" 35 | ] 36 | } 37 | }, 38 | "publishOptions": { 39 | "exclude": [ 40 | "**.xproj", 41 | "**.user", 42 | "**.vspscc" 43 | ], 44 | "include": [ 45 | "appsettings.json", 46 | "ClientApp/dist", 47 | "node_modules", 48 | "Views", 49 | "wwwroot" 50 | ] 51 | }, 52 | 53 | "scripts": { 54 | "precompile": "node node_modules/webpack/bin/webpack.js", 55 | "prepublish": [ 56 | "npm install", 57 | "node node_modules/webpack/bin/webpack.js --config webpack.config.prod.js" 58 | ] 59 | //"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var nodeExternals = require('webpack-node-externals'); 4 | 5 | var clientBundleConfig = { 6 | entry: { 7 | main: ['./ClientApp/boot-client.js'], 8 | }, 9 | output: { 10 | path: path.join(__dirname, 'wwwroot', 'dist'), 11 | filename: '[name].js', 12 | publicPath: '/dist/' 13 | }, 14 | module: { 15 | loaders: [ 16 | { test: /\.jsx?$/, include: /ClientApp/, loader: 'babel-loader' }, 17 | { test: /\.css/, loader: "style!css" }, 18 | { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, 19 | ] 20 | }, 21 | resolve: { 22 | extensions: [ '', '.js', '.jsx' ] 23 | }, 24 | plugins: [ 25 | new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) 26 | ], 27 | devtool: 'inline-source-map' 28 | }; 29 | 30 | var serverBundleConfig = { 31 | entry: { 32 | 'boot-server' : path.join(__dirname, 'ClientApp', 'boot-server.js') 33 | }, 34 | output: { 35 | filename: '[name].js', 36 | path: path.join(__dirname, 'ClientApp', 'dist'), 37 | libraryTarget: 'commonjs' 38 | }, 39 | module: { 40 | loaders: [ 41 | { test: /\.jsx?$/, include: /ClientApp/, loader: 'babel-loader' }, 42 | { test: /\.css/, loader: "style!css" }, 43 | { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, 44 | ] 45 | }, 46 | //target: 'node', 47 | resolve: { 48 | extensions: [ '', '.js', '.jsx' ] 49 | }, 50 | target: 'node', 51 | devtool: 'inline-source-map', 52 | externals: [nodeExternals()] // Don't bundle .js files from node_modules 53 | }; 54 | 55 | module.exports = [clientBundleConfig, serverBundleConfig]; -------------------------------------------------------------------------------- /src/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var pkg = require('./package.json'); 5 | 6 | module.exports = { 7 | entry: { 8 | main: './ClientApp/boot-client.js', 9 | vendor: Object.keys(pkg.dependencies) 10 | }, 11 | output: { 12 | path: path.join(__dirname, 'wwwroot', 'dist'), 13 | filename: '[name].min.js', 14 | }, 15 | module: { 16 | loaders: [ 17 | { test: /\.jsx?$/, include: /ClientApp/, loader: 'babel-loader' }, 18 | { test: /\.css/, loader:ExtractTextPlugin.extract('style', 'css'), }, 19 | { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }, 20 | ] 21 | }, 22 | resolve: { 23 | extensions: [ '', '.js', '.jsx' ] 24 | }, 25 | plugins: [ 26 | new ExtractTextPlugin('site.min.css'), 27 | new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, screw_ie8: true }}), 28 | new webpack.optimize.CommonsChunkPlugin('vendor', '[name].min.js'), 29 | new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), 30 | new webpack.optimize.OccurenceOrderPlugin(), 31 | new webpack.optimize.DedupePlugin(), 32 | new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /src/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xabikos/aspnetcore-react-redux-template/cf3f7682a06b8369d544bbbd24656914de5bed76/src/wwwroot/favicon.ico --------------------------------------------------------------------------------