├── .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 |
{ this.props.increment() } }>Increment
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 | Date
25 | Temp. (C)
26 | Temp. (F)
27 | Summary
28 |
29 |
30 |
31 | {this.props.forecasts.map(forecast =>
32 |
33 | { forecast.dateFormatted }
34 | { forecast.temperatureC }
35 | { forecast.temperatureF }
36 | { forecast.summary }
37 |
38 | )}
39 |
40 |
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 |
17 | Client-side navigation . For example, click Counter then Back to return here.
18 | Webpack dev middleware . In development mode, there's no need to run the webpack
build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.
19 | Hot module replacement . In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, rebuilt CSS and React components will be injected directly into your running application, preserving its live state.
20 | Efficient production builds . In production mode, development-time features are disabled, and the webpack
build tool produces minified static CSS and JavaScript files.
21 | Server-side prerendering . To optimize startup time, your React application is first rendered on the server. The initial HTML and state is then transferred to the browser, where client-side code picks up where the server left off.
22 |
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 |
11 | Toggle navigation
12 |
13 |
14 |
15 |
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
--------------------------------------------------------------------------------