-
Notifications
You must be signed in to change notification settings - Fork 0
/
Notes
346 lines (277 loc) · 23.3 KB
/
Notes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
BACKEND
1.dotnet new sln
created a solution file
# what is sln file: An SLN file is a structure file used for organizing projects in Microsoft Visual Studio. It contains text-based information about the project environment and project state.
2. dotnet new classlib -n fileName(which is Domain, Application and Database here)
created different projects(classes/namespaces)
3. dotnet new webapi -n API
created the api project
4. dotnet sln add Domain/ (and API, Database)
add projects into sln file. We don't need to specify the full path
5. dotnet sln list
see the difference in sln document. Now the all have csproj, because they are all included in the sln file
6. cd into Application
dotnet add reference ../Domain/ (do the same for Database)
add reference for each project so each of them know what each's dependencies are.
7. cd into API
donet add reference ../Application/
add reference for API. pay attention to who add to who here
8. cd into Database
dotnet add reference ../Domain/
9. fn + f5 to add .vscode launch file
10. API/Startup.cs
commet out https stuff
11. Reactivities/API/Properties/launchSettings.json
"applicationUrl": "http://localhost:5000",
12. dotnet run
in API level, run the api server to see different endpoints
13. Here we will start creating database related stuff.
(1)create property fields inside Domain/value.cs change the default class1 to value. Id will be the primary key
(2)use entity framework to scaffold database (like Rails, we use rails g resource to generate models, migration, controller and all that). Change Class1.cs into DataContext.cs, create DataContext class derived from DbContext (we will get error for DbContext, do next step to install it). reference:https://docs.microsoft.com/en-us/ef/core/
(3)cmd + shift + p, search nuget package manager add package, and install Microsoft.EntityFrameWorkCore and Microsoft.EntityFrameWorkCore.sqlite, they need to be the same version as the .net version we are running. Add them to our database project, click restore(this is to updata package, we can also cd into this project level and run dotnet restore)
(4)now hover over DbContext, using what we just installed
(5)create a constructor for DataContext class (refer to the file)
(6)enable query access in our project, so we need to add it to our API project/Startup.cs, inside our dependency indection container. After that go to appsetting.json to set up the source of the db. reference:https://www.jerriepelser.com/tutorials/airport-explorer/basic/working-with-configuration/
14. db migration
dotnet ef migrations add InitialCreate(name) -p Database/ -s API/ stop the server and run this so our multi-projects have access to the db. p means project, s referes to Startup program
(ef -> Entity FrameWork)
15. create database
(1) check if a program has a db already. If not, create one based on migrations. In API/program
(2) cd into API project level, run dotnet watch run (this command only works in the context of Startup project)
we can see reactivities.db file created on this level. pop up shell command and sqlite, open this file, we can see it in sqlite explorer
16. seed data
(1) Database/DataContext seed some data (refer to the file)
(2) When we run migrations, it's gonna insert these data. In root project level, run dotnet ef migration add seedData -p Database/ -s API/
(3) cd into API level, run dotnet watch run, we shall see the seeded data.
17. dependency injection
now we use dependency injection to inject seeded data into ValueController so we can get data from api endpoints (Q to google: in Ruby we can directly make query into db, in dotnet, do we always have to inject our db into controller to have access to db?)
(1) create constructor, inside it there are private fields. conventionally, private variable has an underscore before it. inside vs code setting, search private, add prefix _
(2) change api endpoints. return <Value> instead of <string>, see file in ValueController.cs
(3) make this get ActionResult async. this makes our application more scalable. reason being, when we make a quest into db, it is better to make it async, so this query will be sent into another thread and not block this current thread.
(imagine this like saga to redux store, we have yield to make async. Q to google: thread -> Node single thread, Ruby-> multiple thread ?)
when we use async function, we need to return Task. see file in ValueController.cs(now everything is changed into async), then require EntityFrameWorkCore
(4) do this to "get individula one" method too
18. save repo with git
make sure bin and obj folder are hidden through setting, and also in our gitnore
in setting, search exclude files. this is like gitnore file
(1)in terminal go into the level where you have the sln file, and git status
(2)git init to create a repo
(3)in root level, create a file .gitnore, add bin and other stuff in it (see in the file), and git add . commit
(4) create a new repo on github and create a new repo with the command (the first one)
********************************************
TWO Q TO GOOGLE ABOVE, search before move on
Logic:
1. How are projects API, Application, Database and Domain related to each other?
Domain is Model folder, the classes inside it are models we want to create, we run migration to create tables for the models, which are stoed in Database.
FRONTEND---client side
19. root level npx create-react-app client-side --use-npm --typescript (so it supports typescript, if you have an exisiting react app, you can add typescript too, look here: https://create-react-app.dev/docs/adding-typescript/ )
20. use React class component in App.tsx to call api (to use hooks with typescript, see here:https://fettblog.eu/typescript-react/hooks/#usestate)
setup the initial state and set state after it mounts with componentDidMount
21. fetch API
use axios here to deal with asynchroize. In client side level, npm install axios;
import axios in App.tsx, and make the api call upon mount (see file), axios returns promise, so we use .then to chain them;
23. solve CORS issue
(1)in startup.cs Configure Service, add cors service, create policy to allow client site("http://localhost:3000") use the data.
(2)use the cors policy in Configure.(see in file). Once the data is successfully fetched, we can see from network that in the header there is access control allow origin
24. implement semantic ui
in terminal npm install semantic-ui-react and add the link into our index.html <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css" /> see the documenation here:https://react.semantic-ui.com/usage/
and use it for header
CRUD
In this section we will apply thin API, meaning move the logic in controller to somewhere else, also seed more data
We will learn MediatR
25. add activity entity
in marketplace install C# extensions. right click on Domain folder, create a new C# class Activity, and create its' Properties (see in file)
26. bring activity entity into database
(1) in DataContext, create another DBSet<Activity>
(2) do migration, stop the server, in terminal root level, dotnet ef migrations add "ActivityEntityAdded" -p Database/ -s API/
27. seed the activity data
we will do this in a different way from last time(DbContext.onModelCreating(DbModelBuilder)), because we will have related data later, this will be a better way
(1) in Database, create a new class seed using the same method as 25
(2) create a method seedData (see in file)
(3) use the prepared data snipit (download from author resources), and bring them in in seed.cs by typing S_3.3(which is the prefix of the data) (see in file)
(4) inside the if statement, track the activity entities and save the changes
(5) inside program.cs, seed the data by running the seedData method below migration. So now when the application run, it will seed the data.(see in file) It will generate the primary key with Guid
we can see the content by clicking right on Activity in SQLITE explorer and choose show table. Note blob (primary key) can not be displayed as text, so don't worry about it.
because sqlite doesn't support guid, so it's stored as blob
################????################### (see reference: https://github.com/jbogard/MediatR/wiki)
IRequestHandler is an interface, when we implement it, we will have have a method #Handle that has two parameters, one is Query object, another is CancellationToken.
Query is for GET, command is for POST
28. create query handler(using MediatR) for all activities (List)
all of our handlers will be stored in Application project
(1) add MediatR package. open nuget cmd+shift_p, search Mediatr, add MediatR.extensions.microsoft.dependencyInjection, choose the latest version, install in Application/Application.csproj, then restore(there will be a pop up)
(2) delete the calss1.cs in Application. create a folder Activities, create a new class List in this folder
(3) in List.cs, create Query Class, derived from IRequest (see in file), create Handler class, derived from IRequestHandler
Question: why do we need <> after interface
(4) create constructor for handler, declare DataContext, make it private, readonly, pass in DataContext
(5) make the this query async, get and return the activities from database
29. create the API endpoint in controller for Activity
(1) in API/Controller, create a new class ActivityController, derived from controllerBase (this is MVC controller without view support, because we are using React)
(2) create the route (api/[controller]), add controller binding [ApiController] (reference:https://stackoverflow.com/questions/9494966/difference-between-apicontroller-and-controller-in-asp-net-mvc)
(3) create its constructor, pass in IMediator interface, and initialze the field
(4) create a GET endpoint, name the method List, send the List query method in List.cs
(5) tell startup that we are using MediatR, configure this in dependencyInjection service, and import MediateR (see in file)
(6) test in postman
30. create handler for one activity(Details)
(1) in App/Activities create a new class Details
(2) create a Query class derived from IRequest, return a single activity <Activity> (see in file)
(3) create a handler for query, pass in the Query type and the Activity which is what we will return, and implement interface
(4) create constructor for the handler, pass in DataContext
(5) create another endpoint in Activity for Details, pass in id this time. Now we can test it in postman
at this point if we ask for a id that does not exist, the FindAsycn will return null, if we put something that is not Guid, we will get 400, because of the [ApiController]. We will have validation later
31. CancellationToken
we will not use it in our application, but it's good to know what it is.
This is used to abort one request when the user decide to abort it, like refresh the page. So will gnot keep on going. See download resource lecture
32. create handler
(1) in App/Activities create a new class create
(2) create a class Command derived from IRequest, this time we will not return anything, because it's POST. We will only need to return a reponse so the client side knows it's succeessful
(3) copy paste all the property fields in Activity entity as we need all of them when we create an activity. Just like request.Id in Details Handle
(4) create a class Handler , constructor is DbContext, implement interface
(5) implement handler. The asynchroize method to create an activity. Return type <Unit>. Unit. It returns an empty entity that we will need to use for response
(6) create an activity and add it into db. (see in file). return Unit if the save is succeessful, else throw problem. (see in file)
33. create an endpoint for Create
(1) (see in file)
(2) test from postman using module4 check the pre-request script because we are using guid and date in the post body.
34. boilerplate code for handlers to make efforts easier ()
we will create snippets for both of handler and command (code snippet ref: https://docs.microsoft.com/en-us/visualstudio/ide/code-snippets?view=vs-2019)
(1) on the top of the screen, click Code, and preference => user snippets => new snippets file for Reactivities => name it "handlerSnippets"
(2) google snippet generator, copy paste Query and Handler from Activity/List, description is "Query handler", tab trigger is "qhandler".
(3) at the buttom, it says to declare a placeholder ....., we will make our return value List<Activity> a place holder, so we will use the $ sign here.
(4) replace all the <List<Activity>> with <${1:ReturnObject}>, remove the Handle method here.Copy and paste it in our snippet file
(5) do the same for command, copy and paste Command and handler into generator, description would be Command Handler, tab trigger would be chandler
(6) remove all the properties in this snippet as they will be different from every handler
(7) remove all the properties and context.Activities.Add(activity);
(8) copy and paste thie snippet into the handlerSnippets file.Add it after the first handle
35. create edit handler
(1) in Application/Activities create a new class Edit
(2) use our code snippet by typing in chandler
(3) we will need all the properties for an activity, copy and paste all the property fields from Create
(4) write edit logic for edit: first we need to look for this activity by Guid and throw error if no such activity exist
(5) compare the coming properties with the existing properties, because we only save the different one. Use null-coalescing operator here. if not null, it will be executed (ref:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator)
!!IMPORTANT: Date by default can not be null, we will get an error here, so all we need to do is just to go to that property field, and change Date to optional by adding a ? after the DateTime type
(6) the rest of the methods are the same(come from snippet), track the save, if success return Unit, throw error otherwise
36. create an endpoint(Put) for edit
(1) in Activity controller. Remember to pass in id
(2) test in postman (grab the Id from an activity)
37. create delete handler
(1) in Application/Activity create a new class Delete
(2) create a command by type in chandler (see in file)
(3) in the handler we need to know the Guid of the activity
(4) in the handler logic, find this activity, if exist delete, and return Unit.value(which is empty)
(5) add an endpoint in the controller
(6) test in postman
(7) delete unused import (if you want, I left them there for future reference)
FRONTEND
38.file structure. group files by features
(1) in /src create two folders, app and feature. app is the app as a whole
(2) in /app, create a folder layout. change index.css into styles.css and move it into layout, same to App.tsx
39. call get list of activities api
(1) go into App.tsx, now the default is to get values using axio, change the api.
(2) set the state with the data
(3) add typescript --- add interface for activity, in app folder create a folder called models
(4) create activity.tsx in this folder, write this interface (see in file)
(5) define interface for state in App.tsx
40. Refactor
(1) change App.tsx into arrow function
(2) replace state with hooks, and integrate typescript in (see in file)
(3) replace componentDidMount with useEffect to fetch data
41. Navigation bar
(1) create NavBar.tsx file in feature folder. see file
(2) use semantic ui (https://react.semantic-ui.com/collections/menu/)
(3) replace the header in App.tsx with NavBar, and customize it
(4) create asset folder and add pics from course file into assets folder
(5) styling. edit style.css in layout folder
(6) add a container around list of activities
42. Activity dashboard
(1) create a folder activities in feature, create another folder dashboard, and create a file in it ActivityDashboard.tsx
(2) pass activities data from app to dashboard and use Grid to display activities. delete the activity list in App.tsx. see in file
(3) we will have an error now, because we haven't define the type of the prop that we are passing down. Whenever we are passing props, we need to define it.
do it in the ActivityDashboard file, IProps
(4) in order to use the activities data, we need to give our ActivityDashboard const a type and that should be React.FC, which is react function component type
and pass in IProps, and use activities as props.activities (see in file)
43. create activity list component
(1) create ActivityList.tsx/dashboard
(2) use semantic item in View to display the activities. Replace the List in ActivityDashboard with this ActivityList. see in file
(3) pass the activities data to ActivityList. Same as last ActivityDashboard, define iProps.
44. display activity details
(1) create a folder details in activities, create a file ActivityDetail.tsx in it
(2) use semantic Card for this component. see in file
(3) add a grid column in ActivityDashboard and bring in Detail component. see in file
45. add activity form
This form will allow us to creat and edit activities. We will use semantic formInput and formText
(1) create a folder form in activities, create a file ActivityForm.tsx. Bring in it ActivityDetail.tsx. see in file
46 select activities
(1) add function to View Button. We should store our state in the parent component App.tsx. this selectedActivity should also be from IActivity as we are selecting activity object.
(2) create a function handleSelectActivity that takes in id parameter, and pass this as a prop to ActivityDashboard in App.tsx
(3) define the above prop in ActivityDashboard.tsx
(4) after we define it in ActivityDashboard, pass it to ActivityList component, and we need to define this prop in ActivityList as well. use this function on Button
(5) pass selectedActivity to ActivityDashboard compnent in App.tsx too. define this prop type in component.
(6) do the same to ActivityDetail component in ActivityDashboard.tsx, and use the data.
(7) by now we will get an error in App.tsx that null is not assignable to IActivity. We can tell typescript(override type safty) that we gonna either pass in an activity or null by adding !. see in file.
OR in ActivityDashboard iProps, we can say we will either get selectedActivity or null
in ActivityDashboard, only display this ActivityDetail when it's not null
47 add edit mode
only display the form when we edit
(1) define edit mode state in App.tsx we don't need to tell the type as we are simply using a boolean value.
(2) pass it to ActivityDashboard, and add this to Iprops in ActivityDashboard
(3) condition rendering in ActivityDashboard.tsx
(4) pass setEditMode to ActivityDetail, add it to its iProp.
(5) attach setEditMode to edit button
48 add create activity mode
click create button and show default form (selectedActivity to null and editMode to true)
(1) create a function handler to do the above in App.tsx, and pass it down to NavBar
(2) create Iprops in NavBar, pass this function to the create button
(3) create submit and cancle button in the form
(4) add cancle function, turn off the form. pass setEditMode in ActivityForm. define its Iprops
(5) pass down setSelectActivity down to ActivityDetail from App.tsx, so we can set it to null when we hit cancle button. add this in Iprops
(6) pass it down to ActivityDetail, add this prop
49 initialize form with data
(1) in ActivityDashboard, pass activity also down to ActivityForm. add this prop to Iprop
(2) in ActivityForm, if this activity exist, the return it, otherwise return the activity structure (to map the form). see in file
(3) pass it in hooks and pass the type, and use this activity value to the form
(4) we will get an error for date, just change the IActivity model date type to string(for now)
50 edit form
(1) create a hanlder for input change. add name attribute for all the input. add this handler for all inputs
(2) set the handleChange event type changeEvent (because if we hover over onChange, we can see the texts)
(3) import Form event, chnaneg chanegEvent to from event, import it in
51 handling submission from local state (not persist into db yet)
(1) add hanldcreate and handleEditActivityin App.tsx. pass them down to ActivityDashBoard see in file
(2) add the two props into IProps of ActivityDashboard. Pass the two to ActivityForm, add into IProps.
(3) add them to submit buttons. if id then we are editing otherwise creating. handlesubmit function. see in file
(4) install uuid package, which allows us to create uniq id
(5) in ActivityForm, import uuid. we will get an error "xxxx implicitly has an any type", hover over, follow the instruction npm install @types/uuid. after this, the error will go away.
(6) use uuid, when we create id, just use uuid().
(7) update activity form detail upon submission, setSelectedactivity after editing and creating in App.tsx also set edit mode to false
52 fix initial date data in the form
(1) some other minor issues to fix before the data:
a. when we click view, the edit mode of the form is false. in App.tsx, handleSelectActivity setEditMode to false. so when we click view, it always display the activity (when we swith from create activity button to view)
b. when we click create activity, we want the form to be empty, instead of showing previous activity info. The reason it's doing so is because when we click create, the previous component is not unmounted, so the activity state didn't get update.
in ActivityDashboard, ActivityForm component, give it a key, once the key changed, update the state(That's how React works, if the props is different it will rerender). see in file.
c. change the data format UTC into readable format. In ActivityForm.tsx, Date column, change the prop type value into "datetime-local", in App.tsx, before we set the activity, format the data first. See in App.tsx
53 delete functionality
(1) in App.tsx, create handleDelete function. Pass it down to the list, add it to props.
(2) create a delete button and attach the function to it.
Axios (persist data now)
In this section, we will restructure our code, separate out the api calls
axios vs fetch (built in with react)
a. axios can catch error if the response is not good (so is saga), whereas fetcn cannot
b. axios returns more than just data, it tells other useful info as well
54 setup for typescript
(1) create an api folder in app folder, create agent.ts (why ts => because we are not using React here, so no React JS)
(2) import axios, and define some useful variables and our restful api
(3) define the resulful api call
55 call the apis
(1) call get api =>in App.tsx, replace the old get call. see both in file and agent.ts
(2) call create api =>in App.tsx, call this in handleCreateActivity. see in file
(3) call edit api.
(4) call delete api.
56 add loading indicator
(1) add a sleep function in agent.ts. add this function in the request.get, add another then to trigger the sleep see in file
(2) add this to edit, create, delete
(3) add loading indicator !!!!!! start from here
(4) build the indicator with semantic ui loader, create loadingComponent.tsx in layout.
(5) toggle loading stats in App.tsx, set loading to false after fetch activities, chain it up with another then.
(6) add indicator when we submit. toggle the state in App.tsx, use this when we create, edit and delete activity.
(7) pass this submitting state to the ActivityList.tsx and ActivityForm.tsx
(8) add name, activity id to each button to identify themselves, pass in the id and the event onClick
(9) toggle the e.target state in App.tsx, set the target name after submitting. pass the target to form, and conditional rendering loading
(9) add loading to delete button and submit button