These videos are the reason why I look forward to Mondays! Debugging I think are the best examples of thinking through your problems and possible better approach. Great video as always Tim
You were the one teacher, over all the years, who left an impression on me. You were the best, and I appreciate it so much . Thanks you so much for giving us you professional experience.
This video and the previous one (I haven't seen the 14th and 15th yet) are particularly great because they show things people generally do not: everyone makes mistakes. But the most valuable part is the art of showing how to look for potential solutions, since there are a lot of approaches. Debugging may be hard to do but I'm pretty sure it is much harder to explain on a video, and here it is done greatly.
Thanks! It is hard to create debugging videos because it is like investigating a mystery that you already know the answer to. So, if I show my bugs on screen and debug them live, you get to watch my actual process. You also get to see that I make "stupid" mistakes, which hopefully gives you permission to not feel bad about doing it yourself.
You did such a good job emphasizing everything in your earlier videos I instinctively added "bearer" before the token and was surprised by the debugging process. Even though I knew immediately what the problem was it turned out to be a very helpful debugging process, great job!
If you find that you return an empty record after getting a bearer token, review the ID for the user in the User Table in TRMData, you may find you did not get all of the GUID from the aspnetusers table for that user.
Hi Tim, thanks for your honesty, with this I mean thanks for debugging in real time, most youtubers, just lay out clean videos and make everything look so easy, meanwhile the reality is different, nevertheless, they expect their viewers to learn from them! So again, thanks for keeping it real, and keep up the good work, I will be joining your patreon soon. Request: Please can you make a curse of a "Human Resource Management System or Similar", in .Net Core or any C# related framework, I will be the first buying that. You don't have to make it public, because I know it wont be easy, but that would be great if possible. I will keep an eye for your response on this. Thanks
I am glad you found it valuable. I work hard to make learning C# easier and part of that is showing how messy development really is so that you are ready for it when it happens to you (and you don't feel like an impostor because your code doesn't go together perfectly).
Thanks Tim, much appreciated, being a week behind on the videos I thought it best not to shout at the bearer issue you had as it would disturb everyone around. However I didn't run into this issue as I put the text in by default ages ago thinking it would be needed and I'd forget every week (like you). I added the bearer+space string to the @default parameter, this populates the text area. Class file: TRMDataManager.App_StartAuthorizationOperationFilter.cs operation.parameters .Add(new Parameter { name = "Authorization", @in = "header", description = "access token", required = false, type = "string", @default = "bearer " });
Funny How i didnt understand a thing at this stage initially when this video was uploaded, but now i know everything Tim is talking about, even lol when Tim ddnt use the BEARER keyword at first . continuous learning pays. Thanks Tim!
Hi Tim, Great course, by the way in SQL to get IntelliSense to work properly, I find using alias's for your table helps. e.g SELECT U.ID,FirstName,U.LastName, U.EmailAddress,U.CreateDate FROM [User] U WHERE U.ID = @ID When you type U in the select it should only bring up the column names for that table.
Thank you so much Tim! I learned such important great things from this video. Especially it was great to know how you can get Id from the context itself and not asking for it as an argument. Also I learned that you can use bearer keyword in Swagger to use your JWT token in Api. Great stuff!
Just a couple of quick tricks (shortcuts) In VS 2019 there is no need to delete the class file when creating a new Lib project just rename the file (in Solution Explorer) and it will rename the class for you. The short cut to create a new class file is Shift + Alt + c
Yeah, I know I can rename it but if you do it in the wrong order it can be a problem. Also, if you accidentally hit no on the dialog, things will not all be named correctly. I prefer to just delete it rather than make sure multiple steps go correctly. Thanks for the new class shortcut. I don't think I've used that one.
@@IAmTimCorey Not sure I follow "... but if you do it in the wrong order it can be a problem." The only dialog I get is about changing reference to the class in the project file.
1:08:42 Everyone gets stuck. Me: Yup, definitely stuck this time. Still dealing with spUserLookup issue. Heck, I found out today, my database name had a typo, but got me this far. Solution: Close my eyes, take a deep breath, and...... Nope, I'll fight it tomorrow with fresh brain-power, or may have to make peace with it. Thank you Tim, for all that you do. Amazing series. One of a kind teaching style, and always love the best practices you show us.
Hang in there. I like your approach of stepping away and coming back to it when you are fresh. That generally has helped me throughout my career. You got this, just don't force it.
Btw don't stop with the debugging stuff and allowing errors to be seen in the video, if it was just perfect then how would I know how to correct my mistakes when I inevitably make them. All of this and all of the others is direction, where to look, how to look, what to consider. Need that as much as I need the code. Appreciate your willingness to put that out there and not just edit the video to make yourself look brilliant - which let's face it, you could easily do.
"USER" Is an sql server reserved word as in "create user"... so it has to be in brackets to be taken as a name rather than a keyword.. I like to name my tables as plurals because they contain multiple of what you're describing so that Users table contains more than 1 user ( it's a list of "Users") this eliminates a lot of conflicts with the keywords as well for example "date" is a type but "dates" is not.
Yep, I go back and forth on that. The table holds plurals but we ask for singulars (so "where User.FirstName = 'Tim'" sounds better than "where Users.FirstName = 'Tim'"). You make a good point about the naming conflict though.
If you say so the table Users is a "List of User", like in List which you can name Users. You are right, a table contains multiple of "User" in this case, so a row is a "User". But in SQL standard convention (what I've learned years ago) is to name tables as singular. Nevertheless you also see plurals very often. From database work view singular naming is more consistent and better alphabetized: see plurals: - InvoiceItems - InvoicePayments - InvoiceReceipts - Invoices - InvoiceTaxes and singular names: - Invoice - InvoiceItem - InvoicePayment - InvoiceReceipt - InvoiceTax In a large database project this really matters when working on the database directly ;)
@@SunnyTomcat1 There is no SQL Standard convention of naming the tables. it's just how you learned it or prefer it. But no matter what you use, be consistent. Personally I also like the singular approach as it's easier to automate mapping. And if you go about using plurals you still have to use a singlar if the table would only hold one record (like maybe a config record or something).
I don't like that you have to create a stored procedure for everything you want to do with the DB. I honestly prefer EntityFramework because it automates all of this stuff and I have never had any of the issues you mentioned with it. I also find the code to be a lot simpler and more friendly for newer developers who may not be as familiar with more advanced features, and SQL.
@IAmTimCorey At 32:00 you told that you wanted to use Anonymous type and made the input data type dynamic for template U. Why is that necessary and why not just input it as an UserModel object. I just wanted to know the motivation behind making that decision.
Since Dapper takes the parameters as 'object' then you can just go LoadData(string spName, object parameters, string connectionName) where T is just the return type. I don't know about the connection name parameter in the method, only in case you have several connection strings. Usually, you would pass it in the constructor and initialize the connection string for the class.
Yes, the connectionName is used to identify which connection string to use. I typically make that optional with a default so that you can omit it when you are using the "normal" connection.
Hi Tim, usually you can update the namespace of a class with a quick action! Saves some typing :) EDIT: Haha, I see you explain it in the next video, nevermind! Thank you so much for your tutorials, they're absolutely the best.
I knew I'd forget that 'bearer' prefix at some point, so when we set up that Authorization field to show in each API method, I set the default value to "bearer ", so I could just paste the token after it: In TRMDataManager > App_Start > AuthorizationOperationFilter > in the new Parameter, add @default = "bearer " somewhere (I put it after '@in = "header",' so needed a comma at the end of the line as well)
I am now watching the series and I hope that you answer because after looking in the existing comments it is not asked. Why use dynamic classes, declare new instances or later use dependency injection, instead of making some of them static? They seem to be ideal for static classes and methods since the output of each method is only depended on the input and not the state.
You can't use interfaces on static classes, which means it is harder to replace the class with a new version. This makes it harder to unit test as well. It is generally better to use a "standard" class and using dependency injection to create it as a singleton rather than creating a static class.
32:00 Using `dynamic` is going to make it difficult to unit test. You later add Dependency Injection of the SqlDataAcces object. To test the UserData class you will need to create a mock of the SqlDataAcces object (using moq). Mock sqlDataAccessMock = new(); sqlDataAccessMock.Setup(d => d.LoadDataAsync (It.IsAny(), new {}, It.IsAny())).Returns(_albums); This won`t work since the testing class will be in a separate project and the anonymous type (new {}) will not be able to be re-used. Replacing `new {}` with `It.IsAny()` will not work because the compiler forbids `dynamic` inside LINQ expression trees. The solution is to use `object` instead of `dynamic` and then you can use `It.IsAny()` in the mock setup. sqlDataAccessMock.Setup(d => d.LoadDataAsync (It.IsAny(), It.IsAny(), It.IsAny())).Returns(_albums); See stackoverflow.com/questions/67475719 Is there any reason you would need to use `dynamic` rather than `object' that I have not thought of?
Couple things here. First, what exactly are you testing in the GetUserById method? That we called the right stored procedure? The UserData class exists basically to be an abstraction over the data access. We can just test one layer up and mock the UserData class. Then, no problems. If you really need to mock the SqlDataAccess, you would set U to be an object (or an actual type if you want). The interface just specifies a generic. So, you could accept an object and cast it as necessary. The reason we did dynamic instead of object is that with dynamic, the system can read the properties without a cast. With an object, the properties are not accessible without a cast.
I have a question. Isn't authentication like the last thing you add to a project? The customer might not need authentication for MVP and isn't it very tedious to have to log in all the time to test thing during development?
In .NET Framework projects, if you don't add authentication up front, it is REALLY hard to add in later. However, you do want to add it in up front so that you are building with security in mind. Otherwise you end up relying on no security and then when you add it, you are prone to forgetting to add it everywhere and then you run into a security hole. There are things you can do in testing to make it quicker, though, like hard-coding credentials. Just be careful to use dummy credentials that wouldn't work in production.
yet another great video. In the UserData.cs you use "List GetUserById(..)", Why "List" as you known an Id is unique so it will only ever return 1 User or none, with "List" as a developer I would expect to be able to get multiple result. In the UserController.cs you use "GetById", but you never give an Id and the developer wouldn't know that it would return the current user internally using an Id, so again a bad naming which makes life harder for developers who need to use or maintain it, using good naming is very important when writing decent software. So the method should be called something like "GetCurrentUser" which at least would make more clear what you'll get returned (could also be something like "GetLoggedInUser" or "GetActiveUser").
I could have. I was just returning what the method call gave me (always a list from the database) so that the client could handle any duplicates but I could have done a .FirstOrDefault() on the list and returned just one. As for the GetById, I see your point. This is being consistent with API controller naming, which is why I did it. The user never sees this name, only developers see it.
@@IAmTimCorey How is it consistent with API controller naming if 'we' are the ones that decided the name. And IMHO, it's especially important for the developers to also have consistent names, UserController.GetById as a developer I still have to guess what Id it's using as it doesn't have a parameter Id, so I have to go into the function to actually see what it is returning, even if it had documentation lines above it so I could see in the IDE what it does, it's still better to have the name of the method to actually represent what it does, not having to rely on documentation. In my experience a lot of bugs are introduced due bad naming and expectancy of the developer based on the name. (in a later video you actually change it in UserController from returntype 'List' to 'UserModel' and used the .First) Seeing as Id is based on 'RequestContext.Principal.Identity.GetUserId()' the function would propably be best named 'GetPrincipalUser' or something like that, let's not forget it could be possible that later we actually DO add a function GetById which would let us get a user by it's Id (for instance if we want to add a User editor screen). As always, never forget that the developer is also a user, so it should also be clear to the developer, personally when I haven't worked on a project for quite some time I also forget how things work if I haven't used proper names, and I make just as much mistakes as the rest of us, I'm no super developer.. ;)
@@SuperDre74 There are naming conventions for WebAPI within the .NET Framework. This is a great example of why a well-documented code base is important. As an application becomes more robust we may want to do more with the `GetById` method in the future. An example may be specific use-cases for a business with multiple registers. This is precisely why we have documentation. When sticking to naming conventions that may confuse other developers within a certain context add a comment.
One other possible enhancement, I took the strings for the table and stored procedure and put them in private member variables (that could have been constants I think.) I like the idea of having a variable I can discover especially if there's a chance I might miss type something. However, if you are setting your models and controllers up right I'm not sure you'll see it duplicated that often. I actually pondered putting them in some kind of config file so I could more easily change names of the procedures and tables etc, in one place, but decided its overkill at this point.
The idea is good but at the end of the day, it really isn't helpful I don't think. I can't think of a time when I wanted to swap out the name of a stored procedure but the data in and out was the same. It is better to keep that information close to where you use it so you know where to find it.
Community 2017 complained about the missing brackets for [dbo].[User] also. Video @ 24:36. (I'll update to Community 2019 in early April.) One Question: I haven't logged in yet with the WPF Login screen -- how does the swagger API know it is me already? Does the bearer token let the api know who I am?
That's the hard part about upgrading - you don't know what is the new version and what is something else. In this case, I think the issue is that User is a keyword so we have to put it in brackets. As for the Swagger API, it has no relationship with WPF. If you use the token on Swagger, it knows who you are through that. If you don't use the token, it doesn't know who you are. The token is proof that you are who you say you are (you get it using your username and password).
Don't know if it has been mentioned already but entering just a couple of spaces in the username and/or password enables the login button when using: UserName?.Length != 0 && Password?.Length != 0 so I think using !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password) is a better option.
Yep, we could definitely improve that check. I did the length check because probably you would want to check for a minimum length (not just zero). So the null or whitespace check would need to be in addition to the length check.
Glad you are sticking with it. Take your time, practice what you learn, and go get other tutorials to help (like you are). That's the right approach. It will take time and that is ok.
Because I don't want to mix my security with my product data. By keeping them separate, I don't have the problem of Entity Framework or SSDT stepping on the toes of the other, plus I can assign different permissions to one database vs the other. So I can have a security admin and a product admin, and they don't have to have access to the other part.
Ha! I actually thought immediately 'why isn't he typing in "bearer" before the token'?" Made me go off and look at what other kinds of tokens there are. Found "basic" as well as a few others. Is the "bearer" token the main one that is used or are there other situations where the other token types are more prevalent? I started the video and then had to stop and go watch the "Generics" video. That was good, too. I finished the current video and now I have to rewatch it and actually type in the supplied code. Thanks for all the lessons.
Yeah, one of those "I'm tired and I am missing something obvious" moments. Doh! Bearer is the main token used by ASP.NET. Glad you are enjoying the content and that it is pushing you to learn even more.
I have a question. Why do we need a List as a return type when we are sure we are gonna get only one user when we search by Id using the stored proc spUserLookup?
@@IAmTimCorey I think it's a design choice in regards to the DataAccess class though, no? Dapper does provide methods that will return a single record. Query returns IEnumerable, but QuerySingle and QueryFirst return just T.
Bit late to the party but try out Insomnia, instead of using Postman and/or Swagger for Development. I find it is much less busier than Postman. I am sure that it doesn't have everything Postman does, but so far it has all I need.
Hello Tim, Great tutorial! In the LoadData method on my Query i get an Exception: Input String was not in a correct format (Id) Any idea how that's possible?
I hate to ask two such a simple questions. I must have missed something along the way. 1. I was curious how Swagger and the API documentation translated the route /User so that it executed the method /User/GetById in the UserController. I thought that it must have had something to do with the WebApiConfig, but that didn't appear to be the answer. My next guess was that it possibly had something to do with the GetById being the only method in the class UserController, which brings me to the second question. 2. I added another method as a test to see what would happen. That method was identical to the GetById except I called this new method Test. That further confused me because the second method, Test also showed up under Swagger and the API documentation as /User only this time as a Post Command instead of a Get command. Interestingly enough I ran the method through Postman and not only did /User work, but /User/????? worked as if the method name was unimportant. Any help would be much appreciated.
Swagger reads your API and identifies what calls you can make to it. It then takes that information and creates the calls available. Basically, you are using overloads here, which Swagger knows are possible based upon the routes in your API.
@@IAmTimCorey I am still not sure how Swagger, Postman and localhost:12345/Help all route /User as a Get call to /User/GetByID and /User as a Post to /User/Test. I will just have to accept that they do and that all 3 of them have similar logic to do this. Again, thanks for getting back to me.
if anyone else has problems with null values for user data, remember that the column names in the table must match the ones in the user model i use the underscore naming convention (snake case) for columns and i could get away with setting "Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;" in the SqlDataAccess constructor if your columns/model properties are even more different than that (not recommended), either: - use alias in the stored procedure, ex: if in the table you have a column fName and in the user model you have FirstName you can do: "Select fName as FirstName..."; but this is tiresome - map the column names to the properties: i think Dapper has a way of mapping through attributes, but I'm not completely sure - use the same naming convention normally, with other frameworks, i map the column names to properties even if they're named the same, just to be safe
Yeah, not using the same name will definitely bite you with Dapper. There is a way to map names but I have found it is just easiest to make your property names match.
Autofac goes into WPF, since we want the UI to control the dependencies (Dependency Inversion). In our case, we used SimpleContainer though, since it is built into Caliburn Micro.
@@IAmTimCorey thank you for the answer. but I have a little problem I created an internal interface (ISqlDataAcces ) . I can't manage to inject it into the constructor of (userData class) reason why the class is public so I have to make it also internal with a public constructor. Then I created a public interface (IUserData), to inject (userData class) into wpf. It worked well, but is that how it should go?
@@IAmTimCorey I'm more of a carpentry/drive fast/kill an orc sort of guy myself. Just thought the juxtaposition of AppDevs and "going for a run" was funny. Doesn't exactly fit the stereotype we wear.
On my "TRMData" connection string, I had Trust Server Certificate=False; Application Intent=ReadWrite; Multi Subnet Failover=False And received an error where these keywords are recognized and my User endpoint started working once I removed these from the connection string. Not sure if they may cause a problem in the future thou, but just for reference if anyone else gets an error like this.
Hello, Tim. I'm trying to understand the architecture of the application. correct me, if i'm wrong. so, essentially we have wpf project on a client side. WebApi, database project and library reside on a server. Then client side (wpf) sends requests to WebApi, which can ask for some data from Library and return it as json. and if i understood it right, we created this Library project so we don't depend on the WebApi. Right?
You are close. Think of it as two projects - WebAPI and WPF. Each project has its business logic and data access broken out into a class library. This reduces the reliance on a specific UI to do the job. So, if we decide to replace the WPF UI with a Blazor WebAssembly UI, we can do that easily without replacing our business logic or data access code.
Hi Tim, I am a little bit confused regarding when to use static methods and when not, for example SqldataAccess is a class that doesn't have any state and it should give service for other classes, and you didn't used static methods in it maybe because it will not work with the dependency injection that you are going to apply later on. But can you advice me when to use static methods/ classes?(or maybe should I avoid them?)
The SQL data access code in this series will work with WPF using MVVM. Instead of calling the API, call the SQL database directly. Nothing else needs to change.
Hi Tim, Like to what's your thoughts on when to use dynamic and object. I could not find a convincing answer. Do you have a real-world example? Can explain why you used dynamic instead of object LoadData U parameter. Thanks
Hi Tim and everyone, can anyone help me with "System.InvalidOperationException: The ConnectionString property has not been initialized." when i call the GET/api/User ?
As a Patreon supporter, I want to download ther source code for this lesson at: Tim Corey is creating C# Training | Patreon However your source code zip file links to those posts are in accessible (as far as I can see).
I don’t guarantee three years of historical downloads, just the more recent ones. The links may expire over time. I believe the ones that aren’t working are only for the first phase, though. I don’t intentionally delete them. I just don’t maintain them.
Short answer is- no. But, it helps separate your code into different sections and makes your code much more reusable. For example, if you want to make a second API that uses the same models as the first API, you can just add a reference to the library instead of having to copy all of the code from one API to the other. The Library can then be further extracted to a NuGet package so everyone in a company can have easy access to it, making sharing code across different projects way easier and simpler. Also helps notify more unexperienced developers about stuff they shouldn't be doing. Like Tim just made the class responsible for connecting to the database internal. If you have done that, then that pretty obviously shows that the API should not have anything to do with database connections and SQL stuff at all. There are probably lots of other benefits that I can't think of out of the top of my head, but those alone should be more than enough. Yes, you could just put everything into one project, if you know that you won't be reusing the models or business logic in other projects and if you are certain that people won't abuse the fact that they will have access to stuff that they shouldn't normally have access to. In this case, you would have to add Dapper to the main project, which opens up possibilities for someone down the line to just do a SQL Query in tteh controller, bypassing any security you might have in place in the data access classes.(Yes, they can still do that, but they would have to add dapper as a NuGet, which might make them think twice if what they are doing is correct)
Why do I get Id as null? I getting rest of the data right (first name, etc.) but the Id is null. I have Id in SELECT area in stored procedure, it's nvarchar(128) same as in ApsNetUsers.
Ok, sorry for panic, I had Id in SELECT area in my TRMData project stored procedure but not in server. just forgot to publish this change. Thanks for the course by the way, great job :)
Yep. You just need to be prepared to test everything that is affected by the package (not just everything that uses it). That gets more complicated as your application does. That's why it is best to update everything in the beginning when there is the least impact.
We haven't tackled that in the course yet, but you would need to watch the register event and get the data from it to update the other table. Either that or you could wait until the user logs in for the first time to check to see if their data is in the Users table. If it isn't, use their ID to create a new entry.
@@IAmTimCorey That means if I create a new ASP .NET Core Web project with authentication type individual login then I have to select a register page from scaffold item and override the register model class and use dapper there to create a user based on the registration?
This is truly one of the best tutorials i've seen! Ps: when i ask for the user info i get everything except for the id it shows as null, i used breakpoints to find out that after the dapper query it becomes null, even though the parameter passes the right ID value, any suggestions?
Should have given myself some more time... I figured out I typed the Id field name incorrectly. My asp.net users is 'Id' whereas my dbo.Users has 'UserId'.
Running into an issue but not able to find a solution. When the application runs and it retrieves the user data the Id is returning null. The odd thing is that the data is being returned for FirstName, LastaName, and Email. I looked at the databases for ASPdotNETusers and Users as was suggested a year ago by John S. The Id field is named correctly and I've tried forcing the query to look at Id in the select statement in the SPROC. Any one else experiencing this issue recently?
Found the solution I had multiple instances of the same database that were published under 2 different names. Logically, to me this would not seem to be an issue because I am not referencing that database in the User Data Library or in my connection string. But as things would have it removing the wrongly named .mdf and .ldf files for the folder that holds those database files has the application working as intended.
does using RequestContext.Principal.Identity.GetUserId(); reduce the scalability of the application? Are we coupling to a particular web server by doing this line of code?
Nope. We are tied to Microsoft Identity but that was true before that line. As far as the web server goes, though, we can deploy this to Apache or IIS without an issue.
@IAmTimCorey : did you ever figure out why the SaveData() method doesn't return the ID of the newly inserted record ? I am having the same issue and cannot get it to work as it should (i.e. return the newly inserted ID so that I can use it in my source code). I know that you solved it by creating another stored procedure to retrieve it, but I'd rather find the solution through the SaveData() method.
It is based upon how I set up my database info. I needed to change some things I didn't want to change. Here is a video that shows you how to get variable information out: ua-cam.com/video/eKkh5Xm0OlU/v-deo.html
I've never used Insight.Database. I've been happy with the speed of Dapper and I know it is used in a high-performance environment (Stack Overflow). The other one looks promising though.
An unhandled exception occurred during the execution of web request::::::::::: System.Data.SqlClient.SqlException: Could not find stored procedure "spUserLookup"
@@anonymous75420 make sure you're connecting with a proper connection string. You can get it by browsing SQL Server Explorer in VS. Find you DB, right click, select properties, look for connection string and use that. Also make sure you published your changes from SQL project to your DB and the stored procedure is actually there. You can verify that by going to SQL Server Explorer, you DB, stored procedures and it should be there.
Tim, This series is excellent, but I'm a bit confused/disappointed. As I'm working through it, I thought it would be good to print out the source code and take some notes. Every video says that it is available to Patreon Subscribers, so I signed up. But, that didn't work. What am I missing? Thanks.
I'm sorry for the confusion. The source code to Patreon members was for people who were following along in the series as we went (or roughly so). The course fully concluded two years ago (after going on for three years). Well after the course concluded, I moved all of the source code into a course on my site: www.iamtimcorey.com/courses/build-a-timco-retail-manager-app-series/ That is where you can get all of the source code. If you had been following along and paying $5/month for the code as the course came out, you would have paid about $180 for the course as an early adopter. Now it costs just a little bit more ($197) for the three years worth of content (approximately 49 hours). The Patreon route was never designed for people to pay $5 and get the source code for the entire course at every step. Now as for the videos still pointing you to Patreon, I can't change the video content. However, I did change the link in the description that I direct you to. It now goes to the course with the message "** TimCo source code now at :.." On Patreon, the top message (I can't really pin it for non-members, but I did leave it as the last message so it would be at the top when you go to sign up) is titled "TimCo Retail Manager Source Code". It explains how the source code is no longer on Patreon, why, and where to get it. I'm not sure how else I can flag people down to stop them from trying to buy the source code for $5.
Video 4 - Configuring Swagger. Here is the full playlist so you can follow along and make sure you get each video: ua-cam.com/play/PLLWMQd6PeGY0bEMxObA6dtYXuJOGfxSPx.html
That is more of a secondary benefit. The primary purpose of swagger is to document your API for users. It tells them exactly how to use your API and what is available.
@@IAmTimCorey I really like your style of coding.. i am trying to get a string instead of a list i have tried changing list to string but i am still lost as to how to do this i did a lot of research over the past few days but i am still not able to create the correct code. any small example would be great. Thanks
I don't believe we set it up to be a GUID. I thought we set it up as an auto-incrementing int. If that is the case, check your column to make sure it is set up to auto-increment and make sure you named it right (and used the right type).
I had similar problem and it turned out that when "Id" was needed to add to stored procedure, then i added it in the solution explorer but it didn't work(since it was create command and it already had this I believe). So, I eventually searched the stored procedure from Programmability folder in SQL Server Object Explorer and right click it, Script as -> Alter to. Then add the Id to Select clause and right click on the editor and Execute.. Then it modified the stored procedure in the database it uses with this connectionstring and it worked.
Ho... I can see that now. I added Route attribute to the method. If I don't mention the [Route] attribute and [RoutePrefix], it shows nothing but the controller name. If we have more than 1 method you have to have the attributes.
When I bring up Swagger, under User I see the api/User but do not have a Parameters section to put in the token. I have gone back through the video to make sure the User Controller is correct and has the Authorize specification. Any suggestions?
IAmTimCorey I have a problem with the display of the User table, my ID is null in swagger even if it is displayed in the database itself for the other columns it's all ok
Are you asking why UserData was not one of the references in the reference list? The reason why is because UserData is a class. References are for projects (which are typically dll files when compiled, although you can reference an exe project too). So we only see the projects in the list. Classes are contained inside the project.
IAmTimCorey thank you i solve a problem my next problem is “ could not be resolved because it was built against the NETFramework,Version=4.7.2 framework. This is a higher version than the currently targeted framework NETframework,Version =4.6.1” this my error now how can solve that ??
Please, Can someone tell me why my UserController.cs keeps disappearing from my solution? I can copy and paste it back in from file explorer but it won't stay there.
Not sure if it will solve your problem, but in Solution Explorer you can click an icon "Show All Files". It will show all files that are inside your Project Folder, but not necessarily included to load by opening your Solution Project with Visual Studio. You can then find that UserController.cs in Solution Explorer, right click on it and click on "Include in Project". Hope it helps. Good Luck!
For those that are getting the response 200 Ok but with [] and no actual data, then maybe you have copied over the user data from AspNetUsers to the wrong UserDb. Check to make sure the database you're copying to is not the one under projects!
TOo right.. the Entity Framework one that ASP setsup is usually on LocalDB, but if you are running another version of SQL (like Express), user.sql and others likelyi s there. I screwed up a go through and try to rebuild it twice because I removed the wrong DB once :)
@@IAmTimCorey I'm having this same issue of getting the 200 OK but no data. The connection string is pointing to the correct database("TRMData"). I've been trying to figure this out for several days...re-watching past videos etc...and I can't find what I'm doing wrong. Any help/ideas would be great! Thanks!
@@stevenbusenitz6330 Hey Steven - I had a similar problem as you did - It may sound silly, but make sure you copy the ID correctly from the aspnet entity framework database to your user database. Before you copy it (from the AspNetUsers table), expand the ID field enough to see the whole guid. Then, click the little arrow to the left and you should see the full guid which is about 35-40 characters long. copy and place in the Users table This is the mistake I made when copying. Doing this made it work for me! Best of luck, -Ryan
Hi Tim. I don't know why, my UserController.cs can't reach TRMDataManager.Library. Until now I've been able to follow along, but I'm stuck here. I tried writing "using TRMDataManager.Library.DataAccess" but VS says can't find Library insiste TRMDataManager. It is not looking for TRMDataManager.Library, but inside TRMDataManager. Any idea on why? I even tried to rename TRMDataManager.Library to something else (TRMDataManagerSpace.Library) but them VS says it can't find TRMDataManagerSpace...
I might be confused as to what you are doing but the UserControl should not access the TRMDataManager.Library. Only the API should access that. The ViewModel should only have access to the TRMDesktopUI.Library.
@@IAmTimCorey I'm at the minute 37:30 and I'm trying to follow exactly as you do, inside UserController.cs. When you write "UserData" VS suggests you to add reference to TRMDataManager.Library. However, that's not happening in my case. Even if I write "using TRMDataManager.Library.DataAccess" by myself VS says it can't find the reference...
Ah, sorry, I thought you said a UserControl, not UserController. So I'm not sure what version of VS you are using but it sounds like it isn't updated (the prompt is a newer thing). No worries though, you can do it manually. So what you do is in your API project, right-click on the references entry in the Solution Explorer and select "Add Reference". Under the Projects section, select the TRMDataManager.Library and check the box next to it. Then add a using statement at the top of your class that says "using TRMDataManager.Library.DataAccess" or whatever you need and you should be all set.
I was getting the same thing - figured I would add what fixed it for me in case anyone else is getting the same. If you look at sql server object explorer, you have two instances under SQL server - at least I do and so does Tim. One is (localdb)\MSSQLLocalDB... other is (localdb)\ProjectsV13... at the step where you copy the connnection string from the properties, I got the properties from the ProjectsV13 version which messed me up. I should have got it from the MSSQLLocalDB version
I don't know if someone answered about field name not popping up, but if you use alias like this will give you suggestion of field names SELECT u.FirstName, u.LastName, u.EmailAddress FROM dbo.[User] u WHERE u.AuthUserId = @Id
These videos are the reason why I look forward to Mondays!
Debugging I think are the best examples of thinking through your problems and possible better approach. Great video as always Tim
Thank you!
Me too .. I am looking forward to every Monday & Thursday :-D for Tim's video
You were the one teacher, over all the years, who left an impression on me. You were the best, and I appreciate it so much
.
Thanks you so much for giving us you professional experience.
I appreciate the kind words.
It is SO AWESOME to watch you debug, and to hear your process. I cannot believe how much I'm learning in this course.
I am glad it was so helpful.
When a bug continues happening and you say intereeeeesting, instead of getting mad. That's priceless!
lol
This video and the previous one (I haven't seen the 14th and 15th yet) are particularly great because they show things people generally do not: everyone makes mistakes. But the most valuable part is the art of showing how to look for potential solutions, since there are a lot of approaches. Debugging may be hard to do but I'm pretty sure it is much harder to explain on a video, and here it is done greatly.
Thanks! It is hard to create debugging videos because it is like investigating a mystery that you already know the answer to. So, if I show my bugs on screen and debug them live, you get to watch my actual process. You also get to see that I make "stupid" mistakes, which hopefully gives you permission to not feel bad about doing it yourself.
I really apreciate that you debug in those videos! I learn a lot from it!
Thanks!
The most important part of this video is at th very end. Well said Tim. Thanks very much!
You are welcome.
What I loved so far in these tutorials is to find the bugs and fix it with patiece. Art of Trying :) I learnt alot, Thanks Tim!
Great to hear!
You did such a good job emphasizing everything in your earlier videos I instinctively added "bearer" before the token and was surprised by the debugging process. Even though I knew immediately what the problem was it turned out to be a very helpful debugging process, great job!
Glad it was helpful!
If you find that you return an empty record after getting a bearer token, review the ID for the user in the User Table in TRMData, you may find you did not get all of the GUID from the aspnetusers table for that user.
Thanks for sharing.
Thank you so much
Hi Tim, thanks for your honesty, with this I mean thanks for debugging in real time, most youtubers, just lay out clean videos and make everything look so easy, meanwhile the reality is different, nevertheless, they expect their viewers to learn from them!
So again, thanks for keeping it real, and keep up the good work, I will be joining your patreon soon.
Request: Please can you make a curse of a "Human Resource Management System or Similar", in .Net Core or any C# related framework, I will be the first buying that. You don't have to make it public, because I know it wont be easy, but that would be great if possible. I will keep an eye for your response on this. Thanks
I am glad you found it valuable. I work hard to make learning C# easier and part of that is showing how messy development really is so that you are ready for it when it happens to you (and you don't feel like an impostor because your code doesn't go together perfectly).
Thanks Tim, much appreciated, being a week behind on the videos I thought it best not to shout at the bearer issue you had as it would disturb everyone around. However I didn't run into this issue as I put the text in by default ages ago thinking it would be needed and I'd forget every week (like you). I added the bearer+space string to the @default parameter, this populates the text area.
Class file: TRMDataManager.App_StartAuthorizationOperationFilter.cs
operation.parameters
.Add(new Parameter
{
name = "Authorization",
@in = "header",
description = "access token",
required = false,
type = "string",
@default = "bearer "
});
I appreciate you considering those around me. As for the token solution, looks great! Thanks!
Funny How i didnt understand a thing at this stage initially when this video was uploaded, but now i know everything Tim is talking about, even lol when Tim ddnt use the BEARER keyword at first . continuous learning pays. Thanks Tim!
Excellent!
Hi Tim, Great course, by the way in SQL to get IntelliSense to work properly, I find using alias's for your table helps. e.g
SELECT U.ID,FirstName,U.LastName, U.EmailAddress,U.CreateDate
FROM [User] U
WHERE U.ID = @ID
When you type U in the select it should only bring up the column names for that table.
Thanks!
This continues to be an amazing resource for me, now more than a year later
I am glad to hear it!
25:27 - User is a keyword in SQL, so that's why you would need the brackets?
Yep
Thank you so much Tim!
I learned such important great things from this video. Especially it was great to know how you can get Id from the context itself and not asking for it as an argument. Also I learned that you can use bearer keyword in Swagger to use your JWT token in Api. Great stuff!
I am glad it was helpful.
Just a couple of quick tricks (shortcuts)
In VS 2019 there is no need to delete the class file when creating a new Lib project just rename the file (in Solution Explorer) and it will rename the class for you.
The short cut to create a new class file is Shift + Alt + c
Yeah, I know I can rename it but if you do it in the wrong order it can be a problem. Also, if you accidentally hit no on the dialog, things will not all be named correctly. I prefer to just delete it rather than make sure multiple steps go correctly. Thanks for the new class shortcut. I don't think I've used that one.
@@IAmTimCorey Not sure I follow "... but if you do it in the wrong order it can be a problem." The only dialog I get is about changing reference to the class in the project file.
1:08:42 Everyone gets stuck.
Me: Yup, definitely stuck this time. Still dealing with spUserLookup issue. Heck, I found out today, my database name had a typo, but got me this far.
Solution: Close my eyes, take a deep breath, and...... Nope, I'll fight it tomorrow with fresh brain-power, or may have to make peace with it.
Thank you Tim, for all that you do. Amazing series. One of a kind teaching style, and always love the best practices you show us.
Hang in there. I like your approach of stepping away and coming back to it when you are fresh. That generally has helped me throughout my career. You got this, just don't force it.
Btw don't stop with the debugging stuff and allowing errors to be seen in the video, if it was just perfect then how would I know how to correct my mistakes when I inevitably make them. All of this and all of the others is direction, where to look, how to look, what to consider. Need that as much as I need the code. Appreciate your willingness to put that out there and not just edit the video to make yourself look brilliant - which let's face it, you could easily do.
Great feedback, thank you very much!!
I wish I could fastforward to have the complete serie of TimCo haha
I totally understand that. I want to just turn on the recording and complete the entire thing. Unfortunately, I just don't have the time.
"USER" Is an sql server reserved word as in "create user"... so it has to be in brackets to be taken as a name rather than a keyword.. I like to name my tables as plurals because they contain multiple of what you're describing so that Users table contains more than 1 user ( it's a list of "Users") this eliminates a lot of conflicts with the keywords as well for example "date" is a type but "dates" is not.
Yep, I go back and forth on that. The table holds plurals but we ask for singulars (so "where User.FirstName = 'Tim'" sounds better than "where Users.FirstName = 'Tim'"). You make a good point about the naming conflict though.
If you say so the table Users is a "List of User", like in List which you can name Users. You are right, a table contains multiple of "User" in this case, so a row is a "User".
But in SQL standard convention (what I've learned years ago) is to name tables as singular. Nevertheless you also see plurals very often. From database work view singular naming is more consistent and better alphabetized:
see plurals:
- InvoiceItems
- InvoicePayments
- InvoiceReceipts
- Invoices
- InvoiceTaxes
and singular names:
- Invoice
- InvoiceItem
- InvoicePayment
- InvoiceReceipt
- InvoiceTax
In a large database project this really matters when working on the database directly ;)
@@SunnyTomcat1 There is no SQL Standard convention of naming the tables. it's just how you learned it or prefer it. But no matter what you use, be consistent. Personally I also like the singular approach as it's easier to automate mapping. And if you go about using plurals you still have to use a singlar if the table would only hold one record (like maybe a config record or something).
I don't like that you have to create a stored procedure for everything you want to do with the DB. I honestly prefer EntityFramework because it automates all of this stuff and I have never had any of the issues you mentioned with it. I also find the code to be a lot simpler and more friendly for newer developers who may not be as familiar with more advanced features, and SQL.
@IAmTimCorey
At 32:00 you told that you wanted to use Anonymous type and made the input data type dynamic for template U. Why is that necessary and why not just input it as an UserModel object. I just wanted to know the motivation behind making that decision.
Since Dapper takes the parameters as 'object' then you can just go LoadData(string spName, object parameters, string connectionName) where T is just the return type. I don't know about the connection name parameter in the method, only in case you have several connection strings. Usually, you would pass it in the constructor and initialize the connection string for the class.
Yes, the connectionName is used to identify which connection string to use. I typically make that optional with a default so that you can omit it when you are using the "normal" connection.
"Phew", @IAMTimCorey that was a bit of a battle but learned a lot. Thanks for the lesson.
I’m glad you pushed through.
Hi Tim, usually you can update the namespace of a class with a quick action! Saves some typing :) EDIT: Haha, I see you explain it in the next video, nevermind! Thank you so much for your tutorials, they're absolutely the best.
Cool, thanks!
I knew I'd forget that 'bearer' prefix at some point, so when we set up that Authorization field to show in each API method, I set the default value to "bearer ", so I could just paste the token after it:
In TRMDataManager > App_Start > AuthorizationOperationFilter > in the new Parameter, add
@default = "bearer "
somewhere (I put it after '@in = "header",' so needed a comma at the end of the line as well)
There you go. That's a great idea. Thanks for sharing.
I implemented this with a minor tiny lazy tweak..."bearer "...so I don't have to press the space bar.
Thanks Tim! The best lessons!
Glad you enjoyed it
You're a great guy Tim, thanks a lot.
Thumbed up and Suscribed !
You are welcome.
I am now watching the series and I hope that you answer because after looking in the existing comments it is not asked. Why use dynamic classes, declare new instances or later use dependency injection, instead of making some of them static? They seem to be ideal for static classes and methods since the output of each method is only depended on the input and not the state.
You can't use interfaces on static classes, which means it is harder to replace the class with a new version. This makes it harder to unit test as well. It is generally better to use a "standard" class and using dependency injection to create it as a singleton rather than creating a static class.
32:00 Using `dynamic` is going to make it difficult to unit test. You later add Dependency Injection of the SqlDataAcces object. To test the UserData class you will need to create a mock of the SqlDataAcces object (using moq).
Mock sqlDataAccessMock = new();
sqlDataAccessMock.Setup(d => d.LoadDataAsync
(It.IsAny(), new {}, It.IsAny())).Returns(_albums);
This won`t work since the testing class will be in a separate project and the anonymous type (new {}) will not be able to be re-used.
Replacing `new {}` with `It.IsAny()` will not work because the compiler forbids `dynamic` inside LINQ expression trees.
The solution is to use `object` instead of `dynamic` and then you can use `It.IsAny()` in the mock setup.
sqlDataAccessMock.Setup(d => d.LoadDataAsync
(It.IsAny(), It.IsAny(), It.IsAny())).Returns(_albums);
See stackoverflow.com/questions/67475719
Is there any reason you would need to use `dynamic` rather than `object' that I have not thought of?
anonymous object not type. the anonymous object `new {}` will not be able to be re-used
Couple things here. First, what exactly are you testing in the GetUserById method? That we called the right stored procedure? The UserData class exists basically to be an abstraction over the data access. We can just test one layer up and mock the UserData class. Then, no problems. If you really need to mock the SqlDataAccess, you would set U to be an object (or an actual type if you want). The interface just specifies a generic. So, you could accept an object and cast it as necessary.
The reason we did dynamic instead of object is that with dynamic, the system can read the properties without a cast. With an object, the properties are not accessible without a cast.
I have a question. Isn't authentication like the last thing you add to a project? The customer might not need authentication for MVP and isn't it very tedious to have to log in all the time to test thing during development?
In .NET Framework projects, if you don't add authentication up front, it is REALLY hard to add in later. However, you do want to add it in up front so that you are building with security in mind. Otherwise you end up relying on no security and then when you add it, you are prone to forgetting to add it everywhere and then you run into a security hole. There are things you can do in testing to make it quicker, though, like hard-coding credentials. Just be careful to use dummy credentials that wouldn't work in production.
Just a question, is not better to call IEnumerable instead of List?
No because we aren’t using Entity Framework so there is no advantage and List gives us extra methods.
yet another great video.
In the UserData.cs you use "List GetUserById(..)", Why "List" as you known an Id is unique so it will only ever return 1 User or none, with "List" as a developer I would expect to be able to get multiple result.
In the UserController.cs you use "GetById", but you never give an Id and the developer wouldn't know that it would return the current user internally using an Id, so again a bad naming which makes life harder for developers who need to use or maintain it, using good naming is very important when writing decent software. So the method should be called something like "GetCurrentUser" which at least would make more clear what you'll get returned (could also be something like "GetLoggedInUser" or "GetActiveUser").
I could have. I was just returning what the method call gave me (always a list from the database) so that the client could handle any duplicates but I could have done a .FirstOrDefault() on the list and returned just one. As for the GetById, I see your point. This is being consistent with API controller naming, which is why I did it. The user never sees this name, only developers see it.
@@IAmTimCorey How is it consistent with API controller naming if 'we' are the ones that decided the name. And IMHO, it's especially important for the developers to also have consistent names, UserController.GetById as a developer I still have to guess what Id it's using as it doesn't have a parameter Id, so I have to go into the function to actually see what it is returning, even if it had documentation lines above it so I could see in the IDE what it does, it's still better to have the name of the method to actually represent what it does, not having to rely on documentation. In my experience a lot of bugs are introduced due bad naming and expectancy of the developer based on the name.
(in a later video you actually change it in UserController from returntype 'List' to 'UserModel' and used the .First)
Seeing as Id is based on 'RequestContext.Principal.Identity.GetUserId()' the function would propably be best named 'GetPrincipalUser' or something like that, let's not forget it could be possible that later we actually DO add a function GetById which would let us get a user by it's Id (for instance if we want to add a User editor screen).
As always, never forget that the developer is also a user, so it should also be clear to the developer, personally when I haven't worked on a project for quite some time I also forget how things work if I haven't used proper names, and I make just as much mistakes as the rest of us, I'm no super developer.. ;)
@@SuperDre74 There are naming conventions for WebAPI within the .NET Framework. This is a great example of why a well-documented code base is important. As an application becomes more robust we may want to do more with the `GetById` method in the future. An example may be specific use-cases for a business with multiple registers.
This is precisely why we have documentation. When sticking to naming conventions that may confuse other developers within a certain context add a comment.
One other possible enhancement, I took the strings for the table and stored procedure and put them in private member variables (that could have been constants I think.) I like the idea of having a variable I can discover especially if there's a chance I might miss type something. However, if you are setting your models and controllers up right I'm not sure you'll see it duplicated that often.
I actually pondered putting them in some kind of config file so I could more easily change names of the procedures and tables etc, in one place, but decided its overkill at this point.
The idea is good but at the end of the day, it really isn't helpful I don't think. I can't think of a time when I wanted to swap out the name of a stored procedure but the data in and out was the same. It is better to keep that information close to where you use it so you know where to find it.
Thanks for all your experiens.
You are welcome.
Community 2017 complained about the missing brackets for [dbo].[User] also. Video @ 24:36. (I'll update to Community 2019 in early April.)
One Question: I haven't logged in yet with the WPF Login screen -- how does the swagger API know it is me already? Does the bearer token let the api know who I am?
That's the hard part about upgrading - you don't know what is the new version and what is something else. In this case, I think the issue is that User is a keyword so we have to put it in brackets. As for the Swagger API, it has no relationship with WPF. If you use the token on Swagger, it knows who you are through that. If you don't use the token, it doesn't know who you are. The token is proof that you are who you say you are (you get it using your username and password).
Thanks Tim for your nice explanation, keep up and really it's interesting how you deal with bugs;)
You are welcome.
Don't know if it has been mentioned already but entering just a couple of spaces in the username and/or password enables the login button when using:
UserName?.Length != 0 && Password?.Length != 0 so I think using !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password) is a better option.
Yep, we could definitely improve that check. I did the length check because probably you would want to check for a minimum length (not just zero). So the null or whitespace check would need to be in addition to the length check.
Thanks! I made the code change ...
Gotta say I love it when something doesn't work, makes it all the more interesting to try and solve it :D
It does!
Great as always Tim, im hanging in here, need to go and watch the generics, got a hang of it, but the interfacing is still a bit hard.
Glad you are sticking with it. Take your time, practice what you learn, and go get other tutorials to help (like you are). That's the right approach. It will take time and that is ok.
Just curious. Why not put the Authentication tables inside the same database as your TRMData db?
Because I don't want to mix my security with my product data. By keeping them separate, I don't have the problem of Entity Framework or SSDT stepping on the toes of the other, plus I can assign different permissions to one database vs the other. So I can have a security admin and a product admin, and they don't have to have access to the other part.
Ha! I actually thought immediately 'why isn't he typing in "bearer" before the token'?" Made me go off and look at what other kinds of tokens there are. Found "basic" as well as a few others. Is the "bearer" token the main one that is used or are there other situations where the other token types are more prevalent?
I started the video and then had to stop and go watch the "Generics" video. That was good, too. I finished the current video and now I have to rewatch it and actually type in the supplied code. Thanks for all the lessons.
Yeah, one of those "I'm tired and I am missing something obvious" moments. Doh! Bearer is the main token used by ASP.NET. Glad you are enjoying the content and that it is pushing you to learn even more.
I have a question. Why do we need a List as a return type when we are sure we are gonna get only one user when we search by Id using the stored proc spUserLookup?
Dapper returns a list, even if there is only one record.
@@IAmTimCorey I think it's a design choice in regards to the DataAccess class though, no? Dapper does provide methods that will return a single record. Query returns IEnumerable, but QuerySingle and QueryFirst return just T.
Bit late to the party but try out Insomnia, instead of using Postman and/or Swagger for Development. I find it is much less busier than Postman. I am sure that it doesn't have everything Postman does, but so far it has all I need.
Thanks for the suggestion.
Hello Tim, Great tutorial!
In the LoadData method on my Query i get an Exception: Input String was not in a correct format (Id)
Any idea how that's possible?
Found it! My Id in UserModel was an int instead of a string
Excellent! Glad you worked it out!
Why don't you use EF?
Great series by the way. Thanks for your efforts.
I'm not a big fan of using EF in most cases: www.iamtimcorey.com/blog/137806/entity-framework
@IAmTimCorey do you have any thoughts on using DateTimeOffset vs DateTime? I tend to favor the former.
I usually use UTC time and not worry about it until display.
Great debugging again buddy!!!
Thanks again!
I hate to ask two such a simple questions. I must have missed something along the way.
1. I was curious how Swagger and the API documentation translated the route /User so that it executed the method /User/GetById in the UserController.
I thought that it must have had something to do with the WebApiConfig, but that didn't appear to be the answer. My next guess was that it possibly had something to do with the GetById being the only method in the class UserController, which brings me to the second question.
2. I added another method as a test to see what would happen. That method was identical to the GetById except I called this new method Test. That further confused me because the second method, Test also showed up under Swagger and the API documentation as /User only this time as a Post Command instead of a Get command.
Interestingly enough I ran the method through Postman and not only did /User work, but /User/????? worked as if the method name was unimportant.
Any help would be much appreciated.
Swagger reads your API and identifies what calls you can make to it. It then takes that information and creates the calls available. Basically, you are using overloads here, which Swagger knows are possible based upon the routes in your API.
@@IAmTimCorey
I am still not sure how Swagger, Postman and localhost:12345/Help all route /User as a Get call to /User/GetByID and /User as a Post to /User/Test. I will just have to accept that they do and that all 3 of them have similar logic to do this.
Again, thanks for getting back to me.
if anyone else has problems with null values for user data, remember that the column names in the table must match the ones in the user model
i use the underscore naming convention (snake case) for columns and i could get away with setting "Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;" in the SqlDataAccess constructor
if your columns/model properties are even more different than that (not recommended), either:
- use alias in the stored procedure, ex: if in the table you have a column fName and in the user model you have FirstName you can do: "Select fName as FirstName..."; but this is tiresome
- map the column names to the properties: i think Dapper has a way of mapping through attributes, but I'm not completely sure
- use the same naming convention
normally, with other frameworks, i map the column names to properties even if they're named the same, just to be safe
Yeah, not using the same name will definitely bite you with Dapper. There is a way to map names but I have found it is just easiest to make your property names match.
I haven't finished the video yet...but have you tried putting bearer before your auth token :D
lol keep watching.
please when you add autofac , you add him in (class library ) or in the ( wpf project ) .
thanks for this very usefull and helpful playlist .
Autofac goes into WPF, since we want the UI to control the dependencies (Dependency Inversion). In our case, we used SimpleContainer though, since it is built into Caliburn Micro.
@@IAmTimCorey
thank you for the answer.
but I have a little problem I created an internal interface (ISqlDataAcces
) . I can't manage to inject it into the constructor of (userData class) reason why the class is public so I have to make it also internal with a public constructor. Then I created a public interface (IUserData), to inject (userData class) into wpf.
It worked well, but is that how it should go?
I love how Tim assumes that AppDevs would "go for a run".
Some do. I'm more of a walk/swim/do household chores type of person.
@@IAmTimCorey I'm more of a carpentry/drive fast/kill an orc sort of guy myself.
Just thought the juxtaposition of AppDevs and "going for a run" was funny.
Doesn't exactly fit the stereotype we wear.
On my "TRMData" connection string, I had
Trust Server Certificate=False;
Application Intent=ReadWrite;
Multi Subnet Failover=False
And received an error where these keywords are recognized and my User endpoint started working once I removed these from the connection string. Not sure if they may cause a problem in the future thou, but just for reference if anyone else gets an error like this.
Just delete the unrecognized keywords and it will work
Instead of deleting I deleted the space between two words and it worked for me. like ServerCertificate, MultiSubnetFailover
Hello, Tim. I'm trying to understand the architecture of the application. correct me, if i'm wrong. so, essentially we have wpf project on a client side. WebApi, database project and library reside on a server. Then client side (wpf) sends requests to WebApi, which can ask for some data from Library and return it as json. and if i understood it right, we created this Library project so we don't depend on the WebApi. Right?
You are close. Think of it as two projects - WebAPI and WPF. Each project has its business logic and data access broken out into a class library. This reduces the reliance on a specific UI to do the job. So, if we decide to replace the WPF UI with a Blazor WebAssembly UI, we can do that easily without replacing our business logic or data access code.
Hi Tim,
I am a little bit confused regarding when to use static methods and when not, for example SqldataAccess is a class that doesn't have any state and it should give service for other classes,
and you didn't used static methods in it maybe because it will not work with the dependency injection that you are going to apply later on.
But can you advice me when to use static methods/ classes?(or maybe should I avoid them?)
please add a video on an SQL connection in WPF using MVVM
The SQL data access code in this series will work with WPF using MVVM. Instead of calling the API, call the SQL database directly. Nothing else needs to change.
@@IAmTimCorey thanks. Would you advise using Entity Framework or Dapper?
Your Tutorials are excellent
I would definitely recommend Dapper.
Hi Tim,
Like to what's your thoughts on when to use dynamic and object. I could not find a convincing answer. Do you have a real-world example?
Can explain why you used dynamic instead of object LoadData U parameter.
Thanks
This should help: ua-cam.com/video/UntC0hoeGAQ/v-deo.html
Thank you very much You are The Best !!!!
You are welcome.
Are stored procedures more performant than regular queries?
They can be, yes.
Hi Tim and everyone, can anyone help me with "System.InvalidOperationException: The ConnectionString property has not been initialized." when i call the GET/api/User ?
solved, was missing the connectionString from SqlDataAccess.cs , "using (IDBConnection connection = new SqlConnection(connectionString) ,
Glad you got it figured out.
As a Patreon supporter, I want to download ther source code for this lesson at: Tim Corey is creating C# Training | Patreon However your source code zip file links to those posts are in accessible (as far as I can see).
I don’t guarantee three years of historical downloads, just the more recent ones. The links may expire over time. I believe the ones that aren’t working are only for the first phase, though. I don’t intentionally delete them. I just don’t maintain them.
Does creating the class library separate help with performance?
Short answer is- no.
But, it helps separate your code into different sections and makes your code much more reusable.
For example, if you want to make a second API that uses the same models as the first API, you can just add a reference to the library instead of having to copy all of the code from one API to the other.
The Library can then be further extracted to a NuGet package so everyone in a company can have easy access to it, making sharing code across different projects way easier and simpler.
Also helps notify more unexperienced developers about stuff they shouldn't be doing.
Like Tim just made the class responsible for connecting to the database internal. If you have done that, then that pretty obviously shows that the API should not have anything to do with database connections and SQL stuff at all.
There are probably lots of other benefits that I can't think of out of the top of my head, but those alone should be more than enough.
Yes, you could just put everything into one project, if you know that you won't be reusing the models or business logic in other projects and if you are certain that people won't abuse the fact that they will have access to stuff that they shouldn't normally have access to.
In this case, you would have to add Dapper to the main project, which opens up possibilities for someone down the line to just do a SQL Query in tteh controller, bypassing any security you might have in place in the data access classes.(Yes, they can still do that, but they would have to add dapper as a NuGet, which might make them think twice if what they are doing is correct)
Why do I get Id as null? I getting rest of the data right (first name, etc.) but the Id is null. I have Id in SELECT area in stored procedure, it's nvarchar(128) same as in ApsNetUsers.
Ok, sorry for panic, I had Id in SELECT area in my TRMData project stored procedure but not in server. just forgot to publish this change. Thanks for the course by the way, great job :)
Glad you figured it out.
I think it'll be a good idea to update NuGet packages periodical.
Yep. You just need to be prepared to test everything that is affected by the package (not just everything that uses it). That gets more complicated as your application does. That's why it is best to update everything in the beginning when there is the least impact.
Thank you Tim
You are Welcome.
How do I get back UserId after successfull registration and use it to enter new user into my own Users table?
We haven't tackled that in the course yet, but you would need to watch the register event and get the data from it to update the other table. Either that or you could wait until the user logs in for the first time to check to see if their data is in the Users table. If it isn't, use their ID to create a new entry.
@@IAmTimCorey That means if I create a new ASP .NET Core Web project with authentication type individual login then I have to select a register page from scaffold item and override the register model class and use dapper there to create a user based on the registration?
This is truly one of the best tutorials i've seen!
Ps: when i ask for the user info i get everything except for the id it shows as null, i used breakpoints to find out that after the dapper query it becomes null, even though the parameter passes the right ID value, any suggestions?
My guess is that your property names don't quite match your SQL field names.
@@IAmTimCorey I ended up deleting the user data table and creating a new one, and it seems to have solved my issue. thanks a lot for your answer. :-)
My response body returns my ID = null. I have no idea what causes it. Any same issues that can might be fixed?
Solved.
Glad you figured it out.
Hi John - what was your resolution? I am coming up with a blank body response although my response code is 200
Should have given myself some more time... I figured out I typed the Id field name incorrectly. My asp.net users is 'Id' whereas my dbo.Users has 'UserId'.
AS I was going back through this trying to setup another project I noticed 23.1 Million Downloads for Dapper... in about 2 months time. Wow
It is a really popular library.
Running into an issue but not able to find a solution. When the application runs and it retrieves the user data the Id is returning null. The odd thing is that the data is being returned for FirstName, LastaName, and Email. I looked at the databases for ASPdotNETusers and Users as was suggested a year ago by John S. The Id field is named correctly and I've tried forcing the query to look at Id in the select statement in the SPROC. Any one else experiencing this issue recently?
Found the solution I had multiple instances of the same database that were published under 2 different names. Logically, to me this would not seem to be an issue because I am not referencing that database in the User Data Library or in my connection string.
But as things would have it removing the wrongly named .mdf and .ldf files for the folder that holds those database files has the application working as intended.
I am glad you got it figured out.
does using RequestContext.Principal.Identity.GetUserId(); reduce the scalability of the application? Are we coupling to a particular web server by doing this line of code?
Nope. We are tied to Microsoft Identity but that was true before that line. As far as the web server goes, though, we can deploy this to Apache or IIS without an issue.
@IAmTimCorey : did you ever figure out why the SaveData() method doesn't return the ID of the newly inserted record ? I am having the same issue and cannot get it to work as it should (i.e. return the newly inserted ID so that I can use it in my source code).
I know that you solved it by creating another stored procedure to retrieve it, but I'd rather find the solution through the SaveData() method.
It is based upon how I set up my database info. I needed to change some things I didn't want to change. Here is a video that shows you how to get variable information out: ua-cam.com/video/eKkh5Xm0OlU/v-deo.html
Hi Tim, i was using Insight.Database instead of Dapper, which one is better? There is a difference between them?
I've never used Insight.Database. I've been happy with the speed of Dapper and I know it is used in a high-performance environment (Stack Overflow). The other one looks promising though.
An unhandled exception occurred during the execution of web request::::::::::: System.Data.SqlClient.SqlException: Could not find stored procedure "spUserLookup"
It sounds like you are missing a stored procedure in your database.
@@IAmTimCorey problem solved...
@@anonymous75420 make sure you're connecting with a proper connection string. You can get it by browsing SQL Server Explorer in VS. Find you DB, right click, select properties, look for connection string and use that. Also make sure you published your changes from SQL project to your DB and the stored procedure is actually there. You can verify that by going to SQL Server Explorer, you DB, stored procedures and it should be there.
my default connection was to the aspnet authentication database. I changed it, per @konkey Donk suggestion and it worked
lmao, then I continued watching the video and Tim fixed hahaha
Dapper - currently sitting at 76M downloads . . . up from 16M at the time this video was made. Coincidence?
I'd like to take credit, but the credit definitely goes to the team that makes Dapper. They make an amazing product.
I THINK NOT
1 month after your comment and its at 84.1M downloads. 8M downloads in 1 month! Not bad ;)
Hi Tim. Why didn't I see a User section on my API? I wonder if I missed that part ...
nvm. Just fixed it :)
How? Im having the same issue now :(
How are you getting the Id back in your model and displayed in swagger. You don't select the Id in your stored procedure.
If I am remembering right, we pass the ID into the method so we just pass the ID out as well.
Actually, we do select the Id in the stored procedure. I almost missed that myself.
Thank you very much
You are welcome.
Tim, This series is excellent, but I'm a bit confused/disappointed.
As I'm working through it, I thought it would be good to print out the source code and take some notes. Every video says that it is available to Patreon Subscribers, so I signed up. But, that didn't work. What am I missing? Thanks.
I'm sorry for the confusion. The source code to Patreon members was for people who were following along in the series as we went (or roughly so). The course fully concluded two years ago (after going on for three years). Well after the course concluded, I moved all of the source code into a course on my site: www.iamtimcorey.com/courses/build-a-timco-retail-manager-app-series/
That is where you can get all of the source code. If you had been following along and paying $5/month for the code as the course came out, you would have paid about $180 for the course as an early adopter. Now it costs just a little bit more ($197) for the three years worth of content (approximately 49 hours). The Patreon route was never designed for people to pay $5 and get the source code for the entire course at every step.
Now as for the videos still pointing you to Patreon, I can't change the video content. However, I did change the link in the description that I direct you to. It now goes to the course with the message "** TimCo source code now at :.." On Patreon, the top message (I can't really pin it for non-members, but I did leave it as the last message so it would be at the top when you go to sign up) is titled "TimCo Retail Manager Source Code". It explains how the source code is no longer on Patreon, why, and where to get it. I'm not sure how else I can flag people down to stop them from trying to buy the source code for $5.
when was swagger introduce? I know I followed this series per episode but I have no swagger
Video 4 - Configuring Swagger. Here is the full playlist so you can follow along and make sure you get each video: ua-cam.com/play/PLLWMQd6PeGY0bEMxObA6dtYXuJOGfxSPx.html
Is Swagger an API testing tool just like Postman?
That is more of a secondary benefit. The primary purpose of swagger is to document your API for users. It tells them exactly how to use your API and what is available.
can this only be done with list box, i am trying to grab only one string from the database and then store this string into a variable
You would still do it the same way. Capture the string in a model and then use that model as your data source.
@@IAmTimCorey I really like your style of coding.. i am trying to get a string instead of a list i have tried changing list to string but i am still lost as to how to do this i did a lot of research over the past few days but i am still not able to create the correct code.
any small example would be great. Thanks
Shouting at the screen bearer .....neighbours thinking I have gone mad.
Hopefully you are shouting good stuff, like: "That's awesome, TIM!"
lol
Hello Tim, Sorry for asking this. I followed the project but i don't know what i did wrong. My Id property is returning null instead guid.
I don't believe we set it up to be a GUID. I thought we set it up as an auto-incrementing int. If that is the case, check your column to make sure it is set up to auto-increment and make sure you named it right (and used the right type).
I had similar problem and it turned out that when "Id" was needed to add to stored procedure, then i added it in the solution explorer but it didn't work(since it was create command and it already had this I believe). So, I eventually searched the stored procedure from Programmability folder in SQL Server Object Explorer and right click it, Script as -> Alter to. Then add the Id to Select clause and right click on the editor and Execute.. Then it modified the stored procedure in the database it uses with this connectionstring and it worked.
I don't find the newly added GetById method in swagger. Do we have to update anything?
Thanks
Ho... I can see that now. I added Route attribute to the method. If I don't mention the [Route] attribute and [RoutePrefix], it shows nothing but the controller name. If we have more than 1 method you have to have the attributes.
Not quite sure I follow.
I'm getting "cannot find dbo.spUserLookup" exception. What am I missing? It's in the folder Stored Procedures under dbo
Sounds like the same issue that Tim had in the video, look at 1hr02 to update your connection string
Did you update your database by re-publishing it?
When I bring up Swagger, under User I see the api/User but do not have a Parameters section to put in the token. I have gone back through the video to make sure the User Controller is correct and has the Authorize specification. Any suggestions?
this was put in place a few videos back in the series
I don't remember which video it was, sorry. Start with the Swagger video. If it isn't there, it is soon after.
@@IAmTimCorey I rewatched the Swagger video. I left the c.OperationFilter... line out of the SwaggerConfig.cs file. When I put it in, all worked.
IAmTimCorey I have a problem with the display of the User table, my ID is null in swagger even if it is displayed in the database itself for the other columns it's all ok
It sounds like your ID column has a naming issue. Check to be sure the name matches the column name exactly.
@@IAmTimCorey Thanks for the advice, but I found that I had simply forgotten to select the ID in the stored procedure 😂
hi tim did not appear a UserData in add reference to?
Are you asking why UserData was not one of the references in the reference list? The reason why is because UserData is a class. References are for projects (which are typically dll files when compiled, although you can reference an exe project too). So we only see the projects in the list. Classes are contained inside the project.
IAmTimCorey thank you i solve a problem my next problem is “ could not be resolved because it was built against the NETFramework,Version=4.7.2 framework. This is a higher version than the currently targeted framework NETframework,Version =4.6.1” this my error now how can solve that ??
Install .NET Framework version 4.7.2 (the developer pack) from here: dotnet.microsoft.com/download/dotnet-framework
Please, Can someone tell me why my UserController.cs keeps disappearing from my solution? I can copy and paste it back in from file explorer but it won't stay there.
Not sure if it will solve your problem, but in Solution Explorer you can click an icon "Show All Files". It will show all files that are inside your Project Folder, but not necessarily included to load by opening your Solution Project with Visual Studio. You can then find that UserController.cs in Solution Explorer, right click on it and click on "Include in Project".
Hope it helps. Good Luck!
For those that are getting the response 200 Ok but with [] and no actual data, then maybe you have copied over the user data from AspNetUsers to the wrong UserDb. Check to make sure the database you're copying to is not the one under projects!
Glad you figured that out.
TOo right.. the Entity Framework one that ASP setsup is usually on LocalDB, but if you are running another version of SQL (like Express), user.sql and others likelyi s there. I screwed up a go through and try to rebuild it twice because I removed the wrong DB once :)
@@IAmTimCorey I'm having this same issue of getting the 200 OK but no data. The connection string is pointing to the correct database("TRMData"). I've been trying to figure this out for several days...re-watching past videos etc...and I can't find what I'm doing wrong. Any help/ideas would be great! Thanks!
@@stevenbusenitz6330 Hey Steven - I had a similar problem as you did - It may sound silly, but make sure you copy the ID correctly from the aspnet entity framework database to your user database.
Before you copy it (from the AspNetUsers table), expand the ID field enough to see the whole guid. Then, click the little arrow to the left and you should see the full guid which is about 35-40 characters long. copy and place in the Users table
This is the mistake I made when copying. Doing this made it work for me! Best of luck,
-Ryan
Hi Tim. I don't know why, my UserController.cs can't reach TRMDataManager.Library. Until now I've been able to follow along, but I'm stuck here. I tried writing "using TRMDataManager.Library.DataAccess" but VS says can't find Library insiste TRMDataManager. It is not looking for TRMDataManager.Library, but inside TRMDataManager. Any idea on why? I even tried to rename TRMDataManager.Library to something else (TRMDataManagerSpace.Library) but them VS says it can't find TRMDataManagerSpace...
I might be confused as to what you are doing but the UserControl should not access the TRMDataManager.Library. Only the API should access that. The ViewModel should only have access to the TRMDesktopUI.Library.
@@IAmTimCorey I'm at the minute 37:30 and I'm trying to follow exactly as you do, inside UserController.cs. When you write "UserData" VS suggests you to add reference to TRMDataManager.Library. However, that's not happening in my case. Even if I write "using TRMDataManager.Library.DataAccess" by myself VS says it can't find the reference...
Ah, sorry, I thought you said a UserControl, not UserController. So I'm not sure what version of VS you are using but it sounds like it isn't updated (the prompt is a newer thing). No worries though, you can do it manually. So what you do is in your API project, right-click on the references entry in the Solution Explorer and select "Add Reference". Under the Projects section, select the TRMDataManager.Library and check the box next to it. Then add a using statement at the top of your class that says "using TRMDataManager.Library.DataAccess" or whatever you need and you should be all set.
@@IAmTimCorey I!m using the new 2019 version, but ok, I will do it manually. Thanks!
That's odd. I wonder why it isn't showing up.
My Reponse code is 200, but my response body is []. Trying to track why the output = null
whoops... when I copied over user ID to dbo.User, I didn't copy the ID correctly...
Glad you figured it out.
It also could be you forgot the .First() or .FirstOrDefault() call on that method (or possibly had ToList())
everything works fine,but id is null when I check user data in swagger
Did you set your ID column up to be an IDENTITY column (meaning it auto-increments a value for the ID)?
@@IAmTimCorey yes,but it does not show id that I have pasted in user table from DB used by api
instead of id it shows null
but in table data,id is filled
@@eofjjeifj4z38973498 Did you solve this? I have the same problem, also my created date was set to 0001:01:01T00:00:00
@@antoneriksson208 no,can't find what is wrong with my code...
"Bearer " 49:40 LOL
Yep. I figure it out eventually.
"spUserLookUp does not exist" even after republishng.
also check the casing, I tried changing default connection and does not throw error, do I need to change the database connection?
It sounds like you are publishing to a different database than you are using to run your application.
I was getting the same thing - figured I would add what fixed it for me in case anyone else is getting the same. If you look at sql server object explorer, you have two instances under SQL server - at least I do and so does Tim. One is (localdb)\MSSQLLocalDB... other is (localdb)\ProjectsV13... at the step where you copy the connnection string from the properties, I got the properties from the ProjectsV13 version which messed me up. I should have got it from the MSSQLLocalDB version
I don't know if someone answered about field name not popping up, but if you use alias like this will give you suggestion of field names
SELECT u.FirstName, u.LastName, u.EmailAddress
FROM dbo.[User] u
WHERE u.AuthUserId = @Id
Thanks for sharing.