(refactor): update all packages to latest

This commit is contained in:
Maksym Sadovnychyy 2022-02-08 21:17:17 +01:00 committed by Maksym Sadovnychyy
parent a99f933507
commit fe78191893
69 changed files with 9974 additions and 21445 deletions

2
.gitignore vendored
View File

@ -180,7 +180,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
/node_modules
node_modules/
# RIA/Silverlight projects
Generated_Code/

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2022, MAKS-IT (Maksym Sadovnychyy)
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

72
README.md Normal file
View File

@ -0,0 +1,72 @@
This project template was bootstrapped with [dotnet new reactredux](https://docs.microsoft.com/en-us/aspnet/core/client-side/spa/react-with-redux?view=aspnetcore-6.0), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) template.
```bash
# NET6 + Redux + TypeScript template
dotnet new reactredux
```
> :warning: This repo is mirrored from [MAKS-IT/reactredux](https://git.maks-it.com/MAKS-IT/reactredux)
## Important changes
ClientApp was splitted from `NET6` webapi `Visual Studio` project and all pakages were updated to the latest version with following procedure:
Install `npm-check-updates` tool
```bash
npm install -g npm-check-updates
```
Update the package.json
```bash
ncu --upgrade
```
Validate changes applied to your package.json and run yarn install to install new versions
```bash
yarn install
```
All refactored to use functional components. Due to some compatibility issues with new `react-router` version, the package [Connected react router](https://github.com/supasate/connected-react-router) were replaced with [Redux firs history](https://github.com/salvoravida/redux-first-history).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

17
clientapp/.eslintrc.json Normal file
View File

@ -0,0 +1,17 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": "off",
"@typescript-eslint/indent": ["error", 2],
"semi": ["error", "never"],
"@typescript-eslint/semi": "off",
"no-unexpected-multiline": "error"
}
}

22
clientapp/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
},
{
"type": "pwa-msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}

59
clientapp/package.json Normal file
View File

@ -0,0 +1,59 @@
{
"name": "react_redux_demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "5.1.3",
"history": "5.2.0",
"jquery": "^3.6.0",
"merge": "^2.1.1",
"popper.js": "^1.16.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "7.2.6",
"react-router": "6.2.1",
"react-router-dom": "6.2.1",
"reactstrap": "9.0.1",
"redux": "4.1.2",
"redux-first-history": "^5.0.8",
"redux-thunk": "2.4.1",
"svgo": "2.8.0"
},
"devDependencies": {
"@types/history": "4.7.11",
"@types/jest": "27.4.0",
"@types/node": "17.0.18",
"@types/react": "17.0.39",
"@types/react-dom": "17.0.11",
"@types/react-redux": "7.1.22",
"@types/react-router": "5.1.18",
"@types/react-router-dom": "5.3.3",
"@types/reactstrap": "8.7.1",
"cross-env": "7.0.3",
"eslint-plugin-jsx-a11y": "^6.5.1",
"nan": "^2.15.0",
"react-scripts": "^5.0.0",
"sass": "^1.49.7",
"typescript": "4.5.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "cross-env CI=true react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"lint": "eslint ./src/**/*.ts ./src/**/*.tsx"
},
"eslintConfig": {
"extends": "react-app"
},
"resolutions": {
"url-parse": ">=1.5.0",
"lodash": ">=4.17.21"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -20,7 +20,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>xxxx</title>
<title>react_redux_demo</title>
</head>
<body>
<noscript>

View File

@ -1,6 +1,6 @@
{
"short_name": "xxxx",
"name": "xxxx",
"short_name": "react_redux_demo",
"name": "react_redux_demo",
"icons": [
{
"src": "favicon.ico",

View File

@ -0,0 +1,22 @@
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { MemoryRouter } from 'react-router-dom'
import { App } from './App'
it('renders without crashing', () => {
const storeFake = (state: any) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
})
const store = storeFake({}) as any
ReactDOM.render(
<Provider store={store}>
<MemoryRouter>
<App/>
</MemoryRouter>
</Provider>, document.createElement('div'))
})

53
clientapp/src/App.tsx Normal file
View File

@ -0,0 +1,53 @@
import React, { FC, useEffect } from 'react'
import { Route, Routes } from 'react-router'
//Redux
import { useSelector, useDispatch } from 'react-redux'
import { actionCreators as settingsActionCreators, ISettingsState, IRoute } from './store/reducers/Settings'
// Components
import { DynamicLayout } from './DynamicLayout'
import { DynamicPage } from './DynamicPage'
interface IRouteProp {
path: string,
element?: JSX.Element
}
const NestedRoutes = (routes: IRoute[], tag: string) => {
return routes.map((route: IRoute, index: number) => {
const props: IRouteProp = {
path: route.path
}
if (route.component) {
const page = <DynamicPage tag={route.component} />
props.element = tag ? <DynamicLayout tag={tag}>{page}</DynamicLayout> : page
}
return <Route key={index} { ...props }>{route.childRoutes ? NestedRoutes(route.childRoutes, tag) : ''}</Route>
})
}
interface IReduxState {
settings: ISettingsState
}
const App: FC = () => {
const dispatch = useDispatch()
const { routes } = useSelector((state: IReduxState) => state.settings)
useEffect(() => {
dispatch(settingsActionCreators.requestSettings())
}, [])
return <>
{routes.length > 0 ? <Routes>
{NestedRoutes(routes, 'BasicLayout')}
</Routes> : ''}
</>
}
export {
App
}

View File

@ -0,0 +1,29 @@
import React, { FC } from 'react'
import { BasicLayout } from './layouts'
import { ILayout } from './layouts/interfaces'
interface ILayouts {
[key: string]: React.FC<ILayout>;
}
const layouts: ILayouts = {
BasicLayout: BasicLayout
}
export interface IDynamicLayout {
tag: string,
children: React.ReactNode
}
const DynamicLayout: FC<IDynamicLayout> = (props) => {
const { tag, children } = props
const TagName = layouts[tag]
return <TagName>{children}</TagName>
}
export {
DynamicLayout
}

View File

@ -0,0 +1,28 @@
import React, { FC } from 'react'
import { Home, Counter, FetchData } from './pages'
interface IPages {
[key: string]: React.FC<any>;
}
const pages: IPages = {
Home: Home,
Counter: Counter,
FetchData: FetchData
}
export interface IDynamicPage {
tag: string
}
const DynamicPage: FC<IDynamicPage> = (props) => {
const { tag } = props
const TagName = pages[tag]
return <TagName />
}
export {
DynamicPage
}

View File

@ -0,0 +1,5 @@
const getKeyValue = <T extends object, U extends keyof T>(key: U) => (obj: T) => obj[key];
export {
getKeyValue
}

View File

@ -0,0 +1,5 @@
import { getKeyValue } from './getKeyValue'
export {
getKeyValue
}

23
clientapp/src/index.tsx Normal file
View File

@ -0,0 +1,23 @@
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { HistoryRouter as Router } from "redux-first-history/rr6"
import { Provider } from 'react-redux'
import { createBrowserHistory } from 'history'
import configureStore from './store/configureStore'
import { App } from './App'
import registerServiceWorker from './registerServiceWorker'
// Create browser history to use in the Redux store
// Get the application-wide store instance, prepopulating with state from the server where available.
const { store, history } = configureStore( createBrowserHistory(window))
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>,
document.getElementById('root'))
registerServiceWorker()

View File

@ -0,0 +1,41 @@
import React, { useState } from 'react'
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
import { Link } from 'react-router-dom'
const NavMenu = () => {
const [state, hookState] = useState({
isOpen: false
})
const toggle = () => {
hookState({
isOpen: !state.isOpen
})
}
return <header>
<Navbar className="navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3" light>
<NavbarBrand tag={Link} to="/">react_redux_demo</NavbarBrand>
<NavbarToggler onClick={toggle} className="mr-2"/>
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={state.isOpen} navbar>
<ul className="navbar-nav flex-grow">
<NavItem>
<NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
</NavItem>
</ul>
</Collapse>
</Navbar>
</header>
}
export {
NavMenu
}

View File

@ -0,0 +1,21 @@
import React, { FC } from 'react'
import { Container } from 'reactstrap'
import { NavMenu } from './NavMenu'
import { ILayout } from '../interfaces'
import 'bootstrap/dist/css/bootstrap.css'
import './scss/style.scss'
const BasicLayout: FC<ILayout> = ({ children = null }) => {
return <>
<NavMenu />
<Container>
{children}
</Container>
</>
}
export {
BasicLayout
}

View File

@ -0,0 +1,42 @@
//colors
$color_science_blue_approx: #0366d6;
$color_cerise_approx: #e01a76;
$white: #fff;
$color_denim_approx: #1b6ec2;
$color_fun_blue_approx: #1861ac;
$black_5: rgba(0, 0, 0, .05);
html {
font-size: 14px;
}
a {
color: $color_science_blue_approx;
&.navbar-brand {
white-space: normal;
text-align: center;
//Instead of the line below you could use @include word-break($value)
word-break: break-all;
}
}
code {
color: $color_cerise_approx;
}
.btn-primary {
color: $white;
background-color: $color_denim_approx;
border-color: $color_fun_blue_approx;
}
.box-shadow {
//Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10)
box-shadow: 0 .25rem .75rem $black_5;
}
@media(min-width: 768px) {
html {
font-size: 16px;
}
}

View File

@ -0,0 +1,5 @@
import { BasicLayout } from './basic'
export {
BasicLayout
}

View File

@ -0,0 +1,3 @@
export interface ILayout {
children?: React.ReactNode
}

View File

@ -0,0 +1,29 @@
import React from 'react'
// Redux
import { useDispatch, useSelector } from 'react-redux'
import { actionCreators as counterActionCreators, CounterState } from '../store/reducers/Counter'
interface IReduxState {
counter: CounterState
}
const Counter = () => {
const dispatch = useDispatch()
const counterState = useSelector((state: IReduxState) => state.counter)
const increment = () => {
dispatch(counterActionCreators.increment())
}
return <>
<h1>Counter</h1>
<p>This is a simple example of a React component.</p>
<p aria-live="polite">Current count: <strong>{counterState.count}</strong></p>
<button type="button" className="btn btn-primary btn-lg" onClick={() => { increment() }}>Increment</button>
</>
}
export {
Counter
}

View File

@ -0,0 +1,79 @@
// React
import React, { useEffect } from 'react'
import { Link, useLocation, useParams } from 'react-router-dom'
// Redux
import { useSelector, useDispatch } from 'react-redux'
import { actionCreators as weatherForecastsActionCreators, WeatherForecast, WeatherForecastsState } from '../store/reducers/WeatherForecasts'
interface IReduxState {
weatherForecasts: WeatherForecastsState
}
type IParams = {
startDateIndex: string
}
const FetchData = () => {
const location = useLocation()
const params = useParams<IParams>()
const dispatch = useDispatch()
const { startDateIndex, forecasts, isLoading } = useSelector((state: IReduxState) => state.weatherForecasts)
const ensureDataFetched = () => {
dispatch(weatherForecastsActionCreators.requestWeatherForecasts(
parseInt(params.startDateIndex ? params.startDateIndex : '0', 10)
))
}
// This method is called when the route parameters change
useEffect(() => {
ensureDataFetched()
}, [location.pathname])
const renderForecastsTable = () => {
return <table className='table table-striped' aria-labelledby="tabelLabel">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{forecasts.map((forecast: WeatherForecast) =>
<tr key={forecast.date}>
<td>{forecast.date}</td>
<td>{forecast.temperatureC}</td>
<td>{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
)}
</tbody>
</table>
}
const renderPagination = () => {
const prevStartDateIndex = (startDateIndex || 0) - 5
const nextStartDateIndex = (startDateIndex || 0) + 5
return <div className="d-flex justify-content-between">
<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${prevStartDateIndex}`}>Previous</Link>
{isLoading && <span>Loading...</span>}
<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${nextStartDateIndex}`}>Next</Link>
</div>
}
return <>
<h1 id="tabelLabel">Weather forecast</h1>
<p>This component demonstrates fetching data from the server and working with URL parameters.</p>
{ renderForecastsTable() }
{ renderPagination() }
</>
}
export {
FetchData
}

View File

@ -1,8 +1,8 @@
import * as React from 'react';
import { connect } from 'react-redux';
import * as React from 'react'
const Home = () => (
<div>
const Home = () => {
return <div>
<h1>Hello, world!</h1>
<p>Welcome to your new single-page application, built with:</p>
<ul>
@ -18,6 +18,8 @@ const Home = () => (
</ul>
<p>The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>npm</code> commands such as <code>npm test</code> or <code>npm install</code>.</p>
</div>
);
}
export default connect()(Home);
export {
Home
}

View File

@ -0,0 +1,9 @@
import { Home } from './Home'
import { Counter } from './Counter'
import { FetchData } from './FetchData'
export {
Home,
Counter,
FetchData
}

View File

@ -0,0 +1,105 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
)
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const url = process.env.PUBLIC_URL as string
const publicUrl = new URL(url, window.location.toString())
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl)
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl)
}
})
}
}
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing as ServiceWorker
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.')
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
}
}
}
}
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl: string) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (response.status === 404 || (contentType && contentType.indexOf('javascript') === -1)) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl)
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.')
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister()
})
}
}

View File

@ -0,0 +1,47 @@
import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import { createReduxHistoryContext, reachify } from 'redux-first-history'
import { History } from 'history'
import { ApplicationState, reducers } from './'
export default function configureStore(history: History, initialState?: ApplicationState) {
const {
createReduxHistory,
routerMiddleware,
routerReducer
} = createReduxHistoryContext({ history })
const middleware = [
thunk,
routerMiddleware
]
const rootReducer = combineReducers({
...reducers,
router: routerReducer
})
const enhancers = []
const windowIfDefined = typeof window === 'undefined' ? null : window as any // eslint-disable-line @typescript-eslint/no-explicit-any
if (windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__) {
enhancers.push(windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__())
}
const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers))
const reduxHistory = createReduxHistory(store)
//if you use @reach/router
const reachHistory = reachify(reduxHistory)
return {
store,
history: reduxHistory,
reachHistory
}
}

View File

@ -1,22 +1,25 @@
import * as WeatherForecasts from './WeatherForecasts';
import * as Counter from './Counter';
import * as WeatherForecasts from './reducers/WeatherForecasts'
import * as Counter from './reducers/Counter'
import * as Settings from './reducers/Settings'
// The top-level state object
export interface ApplicationState {
counter: Counter.CounterState | undefined;
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined;
counter: Counter.CounterState | undefined
weatherForecasts: WeatherForecasts.WeatherForecastsState | undefined
settings: Settings.ISettingsState | undefined
}
// Whenever an action is dispatched, Redux will update each top-level application state property using
// the reducer with the matching name. It's important that the names match exactly, and that the reducer
// acts on the corresponding ApplicationState property type.
export const reducers = {
counter: Counter.reducer,
weatherForecasts: WeatherForecasts.reducer
};
counter: Counter.reducer,
weatherForecasts: WeatherForecasts.reducer,
settings: Settings.reducer
}
// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are
// correctly typed to match your store.
export interface AppThunkAction<TAction> {
(dispatch: (action: TAction) => void, getState: () => ApplicationState): void;
(dispatch: (action: TAction) => void, getState: () => ApplicationState): void
}

View File

@ -1,15 +1,15 @@
import { Action, Reducer } from 'redux';
import { Action, Reducer } from 'redux'
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface CounterState {
count: number;
count: number
}
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
// They do not themselves have any side-effects they just describe something that is going to happen.
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
export interface IncrementCountAction { type: 'INCREMENT_COUNT' }
@ -17,32 +17,32 @@ export interface DecrementCountAction { type: 'DECREMENT_COUNT' }
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
export type KnownAction = IncrementCountAction | DecrementCountAction;
export type KnownAction = IncrementCountAction | DecrementCountAction
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).
export const actionCreators = {
increment: () => ({ type: 'INCREMENT_COUNT' } as IncrementCountAction),
decrement: () => ({ type: 'DECREMENT_COUNT' } as DecrementCountAction)
};
increment: () => ({ type: 'INCREMENT_COUNT' } as IncrementCountAction),
decrement: () => ({ type: 'DECREMENT_COUNT' } as DecrementCountAction)
}
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
export const reducer: Reducer<CounterState> = (state: CounterState | undefined, incomingAction: Action): CounterState => {
if (state === undefined) {
return { count: 0 };
}
if (state === undefined) {
return { count: 0 }
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'INCREMENT_COUNT':
return { count: state.count + 1 };
case 'DECREMENT_COUNT':
return { count: state.count - 1 };
default:
return state;
}
};
const action = incomingAction as KnownAction
switch (action.type) {
case 'INCREMENT_COUNT':
return { count: state.count + 1 }
case 'DECREMENT_COUNT':
return { count: state.count - 1 }
default:
return state
}
}

View File

@ -0,0 +1,77 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
export interface IRoute {
path: string,
component?: string,
childRoutes?: IRoute[]
}
export interface ISettingsState {
routes: IRoute[],
isLoading: boolean
}
interface RequestSettingsAction {
type: 'REQUEST_SETTINGS'
}
interface ReceiveSettingsAction {
type: 'RECEIVE_SETTINGS',
routes: IRoute[]
}
type KnownAction = RequestSettingsAction | ReceiveSettingsAction;
export const actionCreators = {
requestSettings: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
const appState = getState()
console.log(appState)
const routes = [
{ path: "/", component: "Home" },
{ path: "/counter", component: "Counter" },
{ path: "/fetch-data",
component: "FetchData",
childRoutes: [
{
path: ":startDateIndex",
component: "FetchData"
}
]
}
]
dispatch({ type: 'RECEIVE_SETTINGS', routes })
dispatch({ type: 'REQUEST_SETTINGS' })
}
}
const unloadedState: ISettingsState = {
routes: [],
isLoading: false
}
export const reducer: Reducer<ISettingsState> = (state: ISettingsState | undefined, incomingAction: Action): ISettingsState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_SETTINGS':
return {
routes: state.routes,
isLoading: true
}
case 'RECEIVE_SETTINGS':
return {
routes: action.routes,
isLoading: false
}
}
return state
}

View File

@ -0,0 +1,100 @@
import { Action, Reducer } from 'redux'
import { AppThunkAction } from '../'
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface WeatherForecastsState {
isLoading: boolean
startDateIndex?: number
forecasts: WeatherForecast[]
}
export interface WeatherForecast {
date: string
temperatureC: number
temperatureF: number
summary: string
}
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
interface RequestWeatherForecastsAction {
type: 'REQUEST_WEATHER_FORECASTS'
startDateIndex: number
}
interface ReceiveWeatherForecastsAction {
type: 'RECEIVE_WEATHER_FORECASTS'
startDateIndex: number
forecasts: WeatherForecast[]
}
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
type KnownAction = RequestWeatherForecastsAction | ReceiveWeatherForecastsAction;
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).
export const actionCreators = {
requestWeatherForecasts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
// Only load data if it's something we don't already have (and are not already loading)
const appState = getState()
if (appState && appState.weatherForecasts && startDateIndex !== appState.weatherForecasts.startDateIndex) {
const requestParams = {
method: 'GET',
headers: { accept: 'application/json', 'content-type': 'application/json' }
}
fetch(`https://localhost:7151/WeatherForecast`, requestParams)
.then((response) => {
const data = response.json() as Promise<WeatherForecast[]>
return data
})
.then((data) => {
dispatch({ type: 'RECEIVE_WEATHER_FORECASTS', startDateIndex: startDateIndex, forecasts: data })
})
dispatch({ type: 'REQUEST_WEATHER_FORECASTS', startDateIndex: startDateIndex })
}
}
}
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
const unloadedState: WeatherForecastsState = { forecasts: [], isLoading: false }
export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState | undefined, incomingAction: Action): WeatherForecastsState => {
if (state === undefined) {
return unloadedState
}
const action = incomingAction as KnownAction
switch (action.type) {
case 'REQUEST_WEATHER_FORECASTS':
return {
startDateIndex: action.startDateIndex,
forecasts: state.forecasts,
isLoading: true
}
case 'RECEIVE_WEATHER_FORECASTS':
// Only accept the incoming data if it matches the most recent request. This ensures we correctly
// handle out-of-order responses.
if (action.startDateIndex === state.startDateIndex) {
return {
startDateIndex: action.startDateIndex,
forecasts: action.forecasts,
isLoading: false
}
}
break
}
return state
}

View File

@ -17,7 +17,8 @@
"dom"
],
"skipLibCheck": false,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"plugins": [{ "name": "typescript-plugin-css-modules" }]
},
"include": [
"src"

8875
clientapp/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
]
}

View File

@ -1,21 +0,0 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
{
"name": "xxxx",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^4.3.1",
"connected-react-router": "6.5.2",
"history": "4.10.1",
"jquery": "^3.5.1",
"merge": "^2.1.1",
"popper.js": "^1.16.0",
"react": "16.11.0",
"react-dom": "16.11.0",
"react-redux": "7.1.1",
"react-router": "5.1.2",
"react-router-dom": "5.1.2",
"reactstrap": "8.1.1",
"redux": "4.0.4",
"redux-thunk": "2.3.0",
"svgo": "1.3.0"
},
"devDependencies": {
"@types/history": "4.7.3",
"@types/jest": "24.0.19",
"@types/node": "12.11.6",
"@types/react": "16.9.9",
"@types/react-dom": "16.9.2",
"@types/react-redux": "7.1.5",
"@types/react-router": "5.1.2",
"@types/react-router-dom": "5.1.0",
"@types/reactstrap": "8.0.6",
"cross-env": "6.0.3",
"eslint-plugin-jsx-a11y": "^6.4.1",
"nan": "^2.14.1",
"react-scripts": "^4.0.3",
"typescript": "3.6.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "cross-env CI=true react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"lint": "eslint ./src/**/*.ts ./src/**/*.tsx"
},
"eslintConfig": {
"extends": "react-app"
},
"resolutions": {
"url-parse": ">=1.5.0",
"lodash": ">=4.17.21"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -1,22 +0,0 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
it('renders without crashing', () => {
const storeFake = (state: any) => ({
default: () => {},
subscribe: () => {},
dispatch: () => {},
getState: () => ({ ...state })
});
const store = storeFake({}) as any;
ReactDOM.render(
<Provider store={store}>
<MemoryRouter>
<App/>
</MemoryRouter>
</Provider>, document.createElement('div'));
});

View File

@ -1,16 +0,0 @@
import * as React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
import Counter from './components/Counter';
import FetchData from './components/FetchData';
import './custom.css'
export default () => (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/fetch-data/:startDateIndex?' component={FetchData} />
</Layout>
);

View File

@ -1,35 +0,0 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { ApplicationState } from '../store';
import * as CounterStore from '../store/Counter';
type CounterProps =
CounterStore.CounterState &
typeof CounterStore.actionCreators &
RouteComponentProps<{}>;
class Counter extends React.PureComponent<CounterProps> {
public render() {
return (
<React.Fragment>
<h1>Counter</h1>
<p>This is a simple example of a React component.</p>
<p aria-live="polite">Current count: <strong>{this.props.count}</strong></p>
<button type="button"
className="btn btn-primary btn-lg"
onClick={() => { this.props.increment(); }}>
Increment
</button>
</React.Fragment>
);
}
};
export default connect(
(state: ApplicationState) => state.counter,
CounterStore.actionCreators
)(Counter);

View File

@ -1,84 +0,0 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { Link } from 'react-router-dom';
import { ApplicationState } from '../store';
import * as WeatherForecastsStore from '../store/WeatherForecasts';
// At runtime, Redux will merge together...
type WeatherForecastProps =
WeatherForecastsStore.WeatherForecastsState // ... state we've requested from the Redux store
& typeof WeatherForecastsStore.actionCreators // ... plus action creators we've requested
& RouteComponentProps<{ startDateIndex: string }>; // ... plus incoming routing parameters
class FetchData extends React.PureComponent<WeatherForecastProps> {
// This method is called when the component is first added to the document
public componentDidMount() {
this.ensureDataFetched();
}
// This method is called when the route parameters change
public componentDidUpdate() {
this.ensureDataFetched();
}
public render() {
return (
<React.Fragment>
<h1 id="tabelLabel">Weather forecast</h1>
<p>This component demonstrates fetching data from the server and working with URL parameters.</p>
{this.renderForecastsTable()}
{this.renderPagination()}
</React.Fragment>
);
}
private ensureDataFetched() {
const startDateIndex = parseInt(this.props.match.params.startDateIndex, 10) || 0;
this.props.requestWeatherForecasts(startDateIndex);
}
private renderForecastsTable() {
return (
<table className='table table-striped' aria-labelledby="tabelLabel">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{this.props.forecasts.map((forecast: WeatherForecastsStore.WeatherForecast) =>
<tr key={forecast.date}>
<td>{forecast.date}</td>
<td>{forecast.temperatureC}</td>
<td>{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
)}
</tbody>
</table>
);
}
private renderPagination() {
const prevStartDateIndex = (this.props.startDateIndex || 0) - 5;
const nextStartDateIndex = (this.props.startDateIndex || 0) + 5;
return (
<div className="d-flex justify-content-between">
<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${prevStartDateIndex}`}>Previous</Link>
{this.props.isLoading && <span>Loading...</span>}
<Link className='btn btn-outline-secondary btn-sm' to={`/fetch-data/${nextStartDateIndex}`}>Next</Link>
</div>
);
}
}
export default connect(
(state: ApplicationState) => state.weatherForecasts, // Selects which state properties are merged into the component's props
WeatherForecastsStore.actionCreators // Selects which action creators are merged into the component's props
)(FetchData as any); // eslint-disable-line @typescript-eslint/no-explicit-any

View File

@ -1,16 +0,0 @@
import * as React from 'react';
import { Container } from 'reactstrap';
import NavMenu from './NavMenu';
export default class Layout extends React.PureComponent<{}, { children?: React.ReactNode }> {
public render() {
return (
<React.Fragment>
<NavMenu />
<Container>
{this.props.children}
</Container>
</React.Fragment>
);
}
}

View File

@ -1,13 +0,0 @@
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
html { font-size: 14px; }
@media (min-width: 768px) {
html { font-size: 16px; }
}
.box-shadow { box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); }

View File

@ -1,42 +0,0 @@
import * as React from 'react';
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';
export default class NavMenu extends React.PureComponent<{}, { isOpen: boolean }> {
public state = {
isOpen: false
};
public render() {
return (
<header>
<Navbar className="navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3" light>
<Container>
<NavbarBrand tag={Link} to="/">xxxx</NavbarBrand>
<NavbarToggler onClick={this.toggle} className="mr-2"/>
<Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={this.state.isOpen} navbar>
<ul className="navbar-nav flex-grow">
<NavItem>
<NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
</NavItem>
</ul>
</Collapse>
</Container>
</Navbar>
</header>
);
}
private toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
}

View File

@ -1,14 +0,0 @@
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
code {
color: #E01A76;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

View File

@ -1,27 +0,0 @@
import 'bootstrap/dist/css/bootstrap.css';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { createBrowserHistory } from 'history';
import configureStore from './store/configureStore';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href') as string;
const history = createBrowserHistory({ basename: baseUrl });
// Get the application-wide store instance, prepopulating with state from the server where available.
const store = configureStore(history);
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root'));
registerServiceWorker();

View File

@ -1,105 +0,0 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const url = process.env.PUBLIC_URL as string;
const publicUrl = new URL(url, window.location.toString());
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing as ServiceWorker;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (response.status === 404 || (contentType && contentType.indexOf('javascript') === -1)) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.');
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@ -1,91 +0,0 @@
import { Action, Reducer } from 'redux';
import { AppThunkAction } from './';
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface WeatherForecastsState {
isLoading: boolean;
startDateIndex?: number;
forecasts: WeatherForecast[];
}
export interface WeatherForecast {
date: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
interface RequestWeatherForecastsAction {
type: 'REQUEST_WEATHER_FORECASTS';
startDateIndex: number;
}
interface ReceiveWeatherForecastsAction {
type: 'RECEIVE_WEATHER_FORECASTS';
startDateIndex: number;
forecasts: WeatherForecast[];
}
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
type KnownAction = RequestWeatherForecastsAction | ReceiveWeatherForecastsAction;
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).
export const actionCreators = {
requestWeatherForecasts: (startDateIndex: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
// Only load data if it's something we don't already have (and are not already loading)
const appState = getState();
if (appState && appState.weatherForecasts && startDateIndex !== appState.weatherForecasts.startDateIndex) {
fetch(`weatherforecast`)
.then(response => response.json() as Promise<WeatherForecast[]>)
.then(data => {
dispatch({ type: 'RECEIVE_WEATHER_FORECASTS', startDateIndex: startDateIndex, forecasts: data });
});
dispatch({ type: 'REQUEST_WEATHER_FORECASTS', startDateIndex: startDateIndex });
}
}
};
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
const unloadedState: WeatherForecastsState = { forecasts: [], isLoading: false };
export const reducer: Reducer<WeatherForecastsState> = (state: WeatherForecastsState | undefined, incomingAction: Action): WeatherForecastsState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_WEATHER_FORECASTS':
return {
startDateIndex: action.startDateIndex,
forecasts: state.forecasts,
isLoading: true
};
case 'RECEIVE_WEATHER_FORECASTS':
// Only accept the incoming data if it matches the most recent request. This ensures we correctly
// handle out-of-order responses.
if (action.startDateIndex === state.startDateIndex) {
return {
startDateIndex: action.startDateIndex,
forecasts: action.forecasts,
isLoading: false
};
}
break;
}
return state;
};

View File

@ -1,29 +0,0 @@
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { connectRouter, routerMiddleware } from 'connected-react-router';
import { History } from 'history';
import { ApplicationState, reducers } from './';
export default function configureStore(history: History, initialState?: ApplicationState) {
const middleware = [
thunk,
routerMiddleware(history)
];
const rootReducer = combineReducers({
...reducers,
router: connectRouter(history)
});
const enhancers = [];
const windowIfDefined = typeof window === 'undefined' ? null : window as any; // eslint-disable-line @typescript-eslint/no-explicit-any
if (windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__) {
enhancers.push(windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__());
}
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}

View File

@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace xxxx.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -1,26 +0,0 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace xxxx.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
private readonly ILogger<ErrorModel> logger;
public ErrorModel(ILogger<ErrorModel> _logger)
{
logger = _logger;
}
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@ -1,3 +0,0 @@
@using xxxx
@namespace xxxx.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace xxxx
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -1,71 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace xxxx
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace xxxx
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -1,49 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.13" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**; $(SpaRoot)build-ssr\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WeatherForecast", "WeatherForecast\WeatherForecast.csproj", "{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{065AC673-3C4D-4C08-B1A9-3C3A1467B3A7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace WeatherForecast.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}

View File

@ -0,0 +1,41 @@
var builder = WebApplication.CreateBuilder(args);
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder => {
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -1,24 +1,28 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57382",
"sslPort": 44367
"applicationUrl": "http://localhost:55971",
"sslPort": 44329
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"WeatherForecast": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7151;http://localhost:5133",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"xxxx": {
"commandName": "Project",
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -0,0 +1,12 @@
namespace WeatherForecast;
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>WeatherForecast</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -2,8 +2,7 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"