Vidly3 Notes -...
Transcript of Vidly3 Notes -...
Vidly3 NotesGithub: https://github.com/mosh-hamedani/vidly-mvc-5
Avoid Null Pointer Exceptions: https://www.journaldev.com/14544/java-lang-nullpointerexception#how-to-fix-javalangnullpointerexception
Section 1 Working with DataPassing Data to Views different waysThe main common way to pass data to a view is to just pass the object to the View.
MovieController
//before creating ViewModel public ActionResult Random() { var movie = new Movie() { Name = "Shrek!" }; return View(movie); }Random.cshtml
@model Vidly3.Models.Movie @{ ViewBag.Title = "Random"; Layout = "~/Views/Shared/_Layout.cshtml";} <h2>@Model.Name</h2>
ViewDataView Data is another way, however must be cast. Do not use ViewData.
MoviesController:
//Passing data to views using ViewData 46:37 ViewData has casting issues. public ActionResult Random() { var movie = new Movie() { Name = "Shrek!" }; ViewData["Movie"] = movie; return View(); }Random.cshtml:
@model Vidly3.Models.Movie @using Vidly3.Models@{ ViewBag.Title = "Random"; Layout = "~/Views/Shared/_Layout.cshtml";}
<h2>@( ((Movie)ViewData["Movie"]).Name)</h2>
ViewBagView bag is another way however it must use Magic properties. Do not use ViewBag
MoviesController:
//ViewBag Dynamic Type 48.53 ViewBag uses Magic Property - no compile time safety - still has casting problem. public ActionResult Random() { var movie = new Movie() { Name = "Shrek!" }; ViewBag.RandomMovie = movie; return View(); }
Random.csthtml
ViewModelsView models allow us to access and combine multiple models. It allows us to combine Customer and Genre properties for example and then access those properties for our view. Where we can access just the properties we require for that view. In our example below in RandomMovieViewModel we have combined a movie and a list of customers.
MoviesController:
// GET: Movies/Randompublic ActionResult Random(){ var movie = new Movie() { Name = "Shrek!" }; var customers = new List<Customer> { new Customer { Name = "Customer 1" }, new Customer { Name = "Customer 2" } }; var viewModel = new RandomMovieViewModel
{ Movie = movie, Customers = customers }; return View(viewModel);}
RandomMovieViewModel:
using System;using System.Collections.Generic;using System.Linq;using System.Web;using Vidly3.Models; namespace Vidly3.ViewModels{ public class RandomMovieViewModel { public Movie Movie { get; set; } public List<Customer> Customers { get; set; } }}
Section 2 Building forms
The Mark-upFirst we need an action.In Controllers open CustomersController
Add a new Action using the shortcut mvcaction4 and then press tab to create an action called New.
//Form to create a new customer public ActionResult New() { var membershiptypes = _context.MembershipTypes.ToList(); var viewModel = new NewCustomerViewModel { MembershipTypes = membershiptypes }; return View(viewModel); }
Add a new View called New Customer
Form LabelsAmending the forms there are two options.
1. Apply a data annotation to the model with – the problem is that this needs to be recompiled.
a. [Display(Name="Date of Birth")] public DateTime? Birthdate { get; set; }
2. The other way is in the form itself we can manually add a label.
a. <div class="form-group"> @Html.LabelFor(m => m.Customer.Birthdate) <label>Date of Birth</label> @Html.TextBoxFor(m => m.Customer.Birthdate, new { @class = "form-control" })
</div>
Focus: When you click on the label and it defaults to the textbox.
If the label is used, the textbox does not focus when you click on the label, unless you add for.
3. <label for="Birthdate">Date of Birth</label>
Drop down ListsAdding a dropdown list for our membership type in the Customer form.
In the Customer Controller we need to get the list of Membershiptypes from the database.
public ActionResult New() { var membershiptypes = _context.MembershipTypes.ToList(); var viewModel = new CustomerFormViewModel { MembershipTypes = membershiptypes }; return View("CustomerForm",viewModel); }
You may find that when you try to add the _context.Membershiptype it is not found, this is because there is no DBSet for MembershipTypes in the IndentityModel“_context.MembershipTypes”
We need to then go and add a DBSet into IdentityModels go to the ApplicationDBContext and add the highlighted line:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public DbSet <Customer> Customers { get; set; } public DbSet <Movie> Movies { get; set; } public DbSet <MembershipType> MembershipTypes { get; set; }
So we can access all the data for this view we must use ViewModels, that encapsulates all the data for this view. We use IEnumerable, because we only need to iterate over the membership types we do not need to add or remove or any other array functions.
public class NewCustomerViewModel { public IEnumerable <MembershipType> MembershipTypes { get; set; }
public Customer Customer { get; set; } }
Back to our CustomerController Action
var viewModel = new CustomerFormViewModel
{ MembershipTypes = membershiptypes};
In the New View we need to add to the start of the View
@model Vidly3.ViewModels.NewCustomerViewModel
@using (@Html.BeginForm("Create", "Customers")){ <div class="form-group"> @Html.LabelFor(m => m.Customer.Name) @Html.TextBoxFor(m => m.Customer.Name, new { @class = "form-control" }) </div> <div class="form-group"> @Html.LabelFor(m => m.Customer.Birthdate) @Html.TextBoxFor(m => m.Customer.Birthdate, "{0:d MMM yyyy}", new { @class = "form-control" }) </div> <div class="form-group"> @Html.LabelFor(m => m.Customer.MembershipTypeId) @Html.DropDownListFor(m => m.Customer.MembershipTypeId, new SelectList(Model.MembershipTypes, "Id", "Name"), "Select Membership Type", new { @class = "form-control" }) </div> <div class="checkbox"> <label> @Html.CheckBoxFor(m => m.Customer.IsSubscribedToNewsletter) Subscribed to Newsletter? </label> </div> <button type="submit" class="btn btn-primary">Save</button>}
Remember to override how it displays on the form in MembershipTypeID in Customers Model
[Display(Name="Membership Type")] public byte MembershipTypeId { get; set; }
Model BindingThe form is ready, now let’s add a button and implement saving a customer.
In the New.cshtml form add a button at the bottom of the form for the submit button to save
<button type="submit" class="btn btn-primary">Save</button>
In the CustomerController implement a new Action with “mvcaction4+tab” shortcut. Change its name to Create.
public ActionResult Create(NewCustomerViewModel viewModel) { return View(); }
To start the model binding first we enter the name of the viewmodel so NewCustomerViewModel and its type viewModel
We run the debugger and you can check the properties that are being sent.
public ActionResult Create(NewCustomerViewModel viewModel)
when you run this in debug mode, you can then change to Customer and customer, and .NET is smart enough to know what we meant.
Place a breakpoint on the return view line and run in debug mode.
We can see that the data was sent
MembershipType is Null
Stop the debugger if we change the type of the parameters to Customer customer .NET is smart enough to bind this object to form data because all the keys where prefixed with Customer
We must declare that [HttpPost] because we do not want a user to be able to use HttpGet because of security reasons. So, we declare that this action can only use Post.
[HttpPost] public ActionResult Create(Customer customer)
Now we can fix the rest of the data:
[HttpPost] public ActionResult Create(Customer customer) { _context.Customers.Add(customer); _context.SaveChanges(); return RedirectToAction("Index", "Customers"); }
Saving DataTo add the customer to the Database, first we have to add our dbcontext
public ActionResult Create(Customer customer) { return View(); }Add:
_context.Customers.Add(customer); return View();
Generated SQL statements at runtime, All these statements are wrapped in a transaction, all these changes will be persisted to the database, or none will be persisted.
Then to persist we need to call SaveChanges:
_context.Customers.Add(customer); _context.SaveChanges(); return View();
Finally, We need to redirect to the list of Customers once complete.
return RedirectToAction("Index", "Customers");
Edit FormWe are going to change the Read Only Details action which when we click on a Customer takes us to a Read Only Details page to the Edit action which will take us to a Edit Form with Editable Edit Text fields.
<tbody> @foreach (var customer in Model) { <tr>
<td>@Html.ActionLink(customer.Name, "Details", "Customers", new { id = customer.Id })</td>
<td>@customer.MembershipType.DiscountRate%</td> </tr> } </tbody>
We are going to change the details action to an Edit action.
<tbody> @foreach (var customer in Model) { <tr> <td>@Html.ActionLink(customer.Name, "Edit", "Customers", new { id = customer.Id }, null)</td> <td>@customer.MembershipType.Name</td> <td>@customer.MembershipType.DiscountRate%</td> </tr> } </tbody>
Updating DataHow to update this customer. We can rename this current action to Save and use it for both Saving a customer and updating a customer, or we can create another Action called Update, and then both can have their own actions for Save and update.
We are going to use the first approach as it is easiest and change the Action to a Save action.
public ActionResult Create(Customer customer) { _context.Customers.Add(customer); _context.SaveChanges(); return RedirectToAction("Index", "Customers"); }Rename action to Save
Does this customer have an ID or not? Otherwise we should update it.
Add an If statement. Note that we are using Single instead of SingleOrDefault because so if the given customer is not found, it will throw an exception. We do not want to deal with a situation where the customer is not found.
Finally, we call Save Changes.
public ActionResult Save(Customer customer) { if(customer.Id ==0) _context.Customers.Add(customer); else { var customerInDb = _context.Customers.Single(c => c.Id == customer.Id); } _context.SaveChanges();
return RedirectToAction("Index", "Customers"); }
Section 3 Implementing ValidationVideo Custom Validation
Implement in Customer.cs Model file
[Min18YearsIfAMember][Display(Name="Date of Birth")]public DateTime? Birthdate { get; set; }
1. Create a new Validation Class called Min18YearsIfAMember – We need to check the selected membership type.
2. Derive that class from ValidationAttributea. public class Min18YearsIfAMember: ValidationAttribute
{3. Validation context gives us access to the containing class ‘Customer’
a. validationContext.ObjectInstance;4. Next Cast it to Customer
a. var customer = (Customer)validationContext.ObjectInstance;5. First, we need to check if customer membership is valid with if statement. Check if
membership type is Pay as you go if its Pay as you go, this is allowed for Children under 18 years.
a. if(customer.MembershipTypeId == 1) { return ValidationResult.Success; }
6. Next, we need to check the Birthdate.a. if(customer.Birthdate == null)
{ return new ValidationResult("Birthdate is required"); }
b.7. Calculate the age then return only if above 18 years of age.
a. var age = DateTime.Today.Year - customer.Birthdate.Value.Year; //.Value because it is nullable
b. return (age >= 18)? ValidationResult.Success : new ValidationResult("Customer should be at least 18 years old to register for this membershiptype");
Now working moving on
Magic NumbersWe need to clean our code.
Not use magic numbers like 0 or 1
In MembershipType.cs add:
public static readonly byte Unknown = 0;
public static readonly byte PayAsYouGo = 1;
Validation SummaryAll errors will be copied to the top header.
In CustomerForm.cshtml and MoviesForm.cshtml at the top add the Validation Summary.
@Html.ValidationSummary()
Remember we had byte assigned for MembershipType which was implicitly required? This is the same with ID in CustomerController, if its instantiated, the values will be created to be 0, then it will have a value, and the error will be gone.
To get rid of the ID Validation we need to go back to CustomerController, in New Action, we have declared the MembershipTypes but not the Customer so instantiate Customer.
//Form to create a new customer public ActionResult New() { var membershiptypes = _context.MembershipTypes.ToList(); var viewModel = new CustomerFormViewModel { Customer = new Customer(), MembershipTypes = membershiptypes }; return View("CustomerForm",viewModel); }
Client-Side ValidationsServer-side Validations on the server are crucial for secure applications, however Doing validations on the Client has many benefits though. Such as immediate feedback for responsive application, and also no wasted server-side resources AND there are also security reasons for also keeping server side validations. To do this we must reference jqueryval located in BundleConfig
1. Immediate feedback.2. No wasted server-side resources.3. Security.
I have first run the application with Server Side Validation and checked the network tab to see any Server Side Validation.
If you check in the _Layout file you can see this line of code:
@RenderSection("scripts", required: false)
This Render section, above allows us to add scripts to our views, and what is put there will be rendered in the page. On pages where jquery validation like forms is required we can reference our Jquery validation bundle to enable client side validation.
Server Side ValidationWith Server Side validation, you can see there was where it says Save a request sent to the server
Client Side ValidationNow I have applied Client Side Validation by adding:
In CustomerForm.cshtml:
@section scripts
{
@Scripts.Render("~/bundles/jqueryval")}
Referencing in BundlesConfig.cs
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( "~/Scripts/jquery.validate*"));
Now when we run the application you can see there is no response sent from the Server.
Anti Forgery TokensTo add antiforgery tokens that will compare a generated code with one that is stored in the users cookies to prevent a hacker from stealing a session, we must add
In the CustomerForm in the Hidden field section
//hidden field [email protected](m => m.Customer.Id)@Html.AntiForgeryToken()<button type="submit" class="btn btn-primary">Save</button>
In the Customer Controller
[HttpPost]//Create was changed to Save [ValidateAntiForgeryToken] public ActionResult Save(Customer customer) { //Checking Validations or CustomerForm if (!ModelState.IsValid)……
As you can see the random generated encrypted code stored in the cookie can be seen, starting “52r79vYW….
If you wanted to test to see if we can hack our site.
Run the application, and inspect element and modify the cookie code.
Once it is modified it is simulating if a hacker did not have the Anti forgery code.
An Exception is received.
Is this the reason why drop downs and selectors may be more beneficial because the edit text input fields have this problem where a hacker could insert code? Anywhere we have an Edit Text field we should use Anti Forgery tokens for our cookies.
Section 3 Review Video1. Add validation to the Movie form and Movie Controller2. Ensure the following:
Release date is automatically entered Number in stock is initialised to 0 Don’t worry about these default numbers Validation, all fields are required. Number in stock if 0 is entered, a validation says number must be between 1 and 20
3. Add Client-Side Validation DONE4. Add Anti Forgery token DONE5. Add to the number in stock a range annotation ensuring it is between 1-20 DONE
Working Made a copy of this version of the project.
Now viewing Code Review video
Section 4 Building Restful ServicesWhat is a Web APIWhen a request arrives at our application, MVC framework hands off that request to an Action in the Controller, this action will return a view. Which is parsed by Razor view engine and then HTML mark-up is generated on the server and returned to the Client. The alternative way is to generate the HTML view on the client itself and send only the Data to the Client. Each Client will be responsible for generating their own views.
Request is sent to our application
HTML is generated on the Server and sent back to the Client.
There is another way though. The Data can be sent back to the Clients and each Client can generate that HTML View on the Client Itself saving Server resources.
Data Generated on the Client using Web API
A Web API allows data to be sent and then processed by the Client itself rather than the server generating an entire HTML page and sending that entire HTML page to the client. This saves server resources.
There are a number of benefits:
1. Less server resources (Improved scalability)2. Less bandwidth (Improved performance)3. Improved support for a broad range of clients – Web Client, mobile app, tablet app –
generated locally
ASP .NET Web APIASP .NET Core has merged ASP .NET MVC with ASP.NET Web API
JQuery Plugin Data Table will allow us to send data.
Type of Request Address DescriptionGET /api/customers Get list of Customers
GET /api/customers/1 Get a customer add the id
POST /api/customers Add a customer send http POST to this end point – POST to create
PUT /api/customers/1 Save a customer
DELETE /api/customers/1 Delete a customerBuilding an APIAdd a new Folders to Controllers called API. Right click the folder click add Controller select “WebAPI 2 Controller – Empty”
Open Global.asax – at the application start add the following lines to the beginning of application start.
GlobalConfiguration.Configure(WebApiConfig.Register)
Create a new Folder in Controllers called “Api”
Create a new API Controller in the new “Api” folder called CustomersController
Customer APINote: that Customer controller is derived from ApiController instead of controller.
We enter all the actions required to query a customer or customers in the database.
public class CustomersController : ApiController { private ApplicationDbContext _context;
public CustomersController() { _context = new ApplicationDbContext(); } //Get /api/customers public IEnumerable<Customer> GetCustomers() { return _context.Customers.ToList(); }
//Get /api/customers/1 public Customer GetCustomer(int id) { var customer = _context.Customers.SingleOrDefault(c => c.Id == id);
if (customer == null) throw new HttpResponseException(HttpStatusCode.NotFound);
return customer; }
//POST /api/customers //to return the resource. //Post request specified [HttpPost] public Customer CreateCustomer(Customer customer) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
_context.Customers.Add(customer); _context.SaveChanges();
return customer; }
//PUT /api/customers/1 [HttpPut] public void UpdateCustomer(int id, Customer customer) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
var customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id);
if(customerInDb ==null) throw new HttpResponseException(HttpStatusCode.NotFound);
customerInDb.Name = customer.Name; customerInDb.Birthdate = customer.Birthdate; customerInDb.IsSubscribedToNewsletter = customer.IsSubscribedToNewsletter; customerInDb.MembershipTypeId = customer.MembershipTypeId;
_context.SaveChanges(); }
//DELETE /api/customers/1 [HttpDelete]
public void DeleteCustomer(int id) { var customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id);
if (customerInDb == null) throw new HttpResponseException(HttpStatusCode.NotFound); _context.Customers.Remove(customerInDb); _context.SaveChanges(); } }
How to test this APIBack in our browser if we change the URL to /api/customers we receive the list of the customers is returned as XML see below. WebAPI has a media formatter, the list of customers will be formatted as below. This is because the header automatically will format as XML if there is no content type header set.
There is no Content-Type for the Request Header so it will default to XML.
Later to get a JSON request in Postman we will set the Content-Type for the request header to application/json
PostmanPostman can be used to help us test each of the actions of our application.
Download and install postman.
Launch from start menu or desktop icon.
With postman we can test if all our Actions are functional. Such as Save, Add Edit or Delete.
Create a basic Request. Save
Save that request and Click Save to First Test
Copy the URL. http://localhost:57416/api/customers from our run project.
Paste into Enter request URL in Postman. Click Send.
You should get a result. Note you can switch the result from JSON and XML and from GET Requests to POST Requests, or DELETE request
JSON
We can see the list of customers as either JSON or XML. Mostly we will be using JSON because that is native for our JavaScript code. JSON code is more lightweight, because there are no noisy opening and closing tags.
Change to XML or JSON
XML
Now we want to test Saving a Customer.Change to JSON, select one of the customers and copy with ctrl+c and change the request type to POST.
Click on the Body Tab and Click on the Raw radio button and paste in the customer. Remove the ID as it will automatically generate a new ID when we send it.
We need to Click on the Headers tab and make sure there is a content-type = application/json
Now Click Send.
If it has run Successfully it will show you 200OK and also in the below window will show what was generated.
it will generate a Saved Customer generating a new ID 19 in this case.
Data Transfer DTOThere are a number of reasons for wanting to use DTO’s Improve performance (Get all data in one call), Flatten object hierarchy, Exclude properties
http://computerauthor.blogspot.com/2015/11/data-transfer-object-design-pattern-in-c.html
https://martinfowler.com/eaaCatalog/dataTransferObject.html
https://docs.microsoft.com/en-us/aspnet/web-api/overview/data/using-web-api-with-entity-framework/part-5’
Right now, our web API exposes the database entities to the client. The client receives data that maps directly to your database tables. However, that's not always a good idea. Sometimes you want to change the shape of the data that you send to client. For example, you might want to:
Remove circular references (see previous section). Hide particular properties that clients are not supposed to view. Omit some properties in order to reduce payload size. Flatten object graphs that contain nested objects, to make them more convenient for clients. Avoid "over-posting" vulnerabilities. (See Model Validation for a discussion of over-
posting.) Decouple your service layer from your database layer.
To accomplish this, you can define a data transfer object (DTO). A DTO is an object that defines how the data will be sent over the network. Let's see how that works with the Book entity. In the Models folder, add two DTO classes:
We will start to use DTO’s for Data Transfer. Currently with our API, it returns Customer objects. This information can change frequently and break, existing clients that are dependent on the Customer object. For example, we may remove or change a property that can impact the client that depends on that property. We have to make the contract of the API as stable as possible. Changing reasonably slower than our Model.
Your API should never receive or return domain objects. It should instead use a DTO. DTO’s are Data Transfer Objects used to transfer data from the Client to the Server or vice
versa. Using a DTO, there is less chance that our API will break as we refactor our domain model.
API versioning will be covered later.
1. Add a new folder in Solution Explorer called DTO’s2. Create a new Class in that folder called CustomerDto
3. Open Customer model and copy the attributes.4. Import DataAnnotations and 5. Delete the line MembershipTypes: because this is a domain class and this is creating a
dependency from our Dto to our domain model. We either use primitive types or custom Dtos. That way it will create a custom Dto like: MembershipTypeDto (Which we will do later)
6. Delete all the Display attributes
public class CustomerDto { public int Id { get; set; } [Required(ErrorMessage = "Please enter a customer's name")] [StringLength(255)] public string Name { get; set; } public bool IsSubscribedToNewsletter { get; set; } public MembershipType MembershipType { get; set; } DELETE THIS LINE [Display(Name = "Membership Type")] DELETE THIS LINE public byte MembershipTypeId { get; set; } [Min18YearsIfAMember] [Display(Name = "Date of Birth")] DELETE THIS LINE public DateTime? Birthdate { get; set; }}
Auto MapperMapping these objects manually to the Api CustomerController can be time consuming. There is an easier way we can use Auto Mapper.
1. Open Package Manager Console: install-package automapper -version:4.1
2. Now create a mapping profile in solution explorer create a new class in App_Start folder called MappingProfile
3. Derive from: Profile
namespace Vidly3.App_Start{ public class MappingProfile : Profile { public MappingProfile() { Mapper.CreateMap<Customer, CustomerDto>(); Mapper.CreateMap<CustomerDto, Customer>(); } }}
We have mapped a Customer to a CustomerDto and a CustomerDto to a Customer. When we run this create map method, it will automapper uses reflection to scan these
types, finds their properties based on their names, which is why we call it a convention-based mapping tool.
1. Here is the mapping profile, we need to load this when the application is started. Open Global.asax
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Http;using System.Web.Mvc;using System.Web.Optimization;using System.Web.Routing;using AutoMapper;using Vidly3.App_Start; namespace Vidly3{ public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { Mapper.Initialize(c=>c.AddProfile<MappingProfile>()); GlobalConfiguration.Configure(WebApiConfig.Register); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } }}
2. In the Controller in Get Customers method in api/CustomerController we will be modifying our actions to map these objects to CustomerDto
3. Get Customers method, Changing the return type to IEnumerable of <CustomerDto>4. We need to map these objects to CustomerDto we pass a delegate by using:
Select(Mapper.Map<Customer, CustomerDto>);
List of Customers DtoFrom
//Get /api/customers public IEnumerable<Customer> GetCustomers() { return _context.Customers.ToList(); }To
//Get /api/customers public IEnumerable<CustomerDto> GetCustomers() { return _context.Customers.ToList().Select(Mapper.Map<Customer, CustomerDto>); }
Update Customer DtoFrom
[HttpPost] public Customer CreateCustomer(Customer customer) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest); _context.Customers.Add(customer); _context.SaveChanges(); return customer; }
//PUT /api/customers/1 [HttpPut] public void UpdateCustomer(int id, Customer customer) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest); var customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id); if(customerInDb ==null) throw new HttpResponseException(HttpStatusCode.NotFound); customerInDb.Name = customer.Name; customerInDb.Birthdate = customer.Birthdate; customerInDb.IsSubscribedToNewsletter = customer.IsSubscribedToNewsletter; customerInDb.MembershipTypeId = customer.MembershipTypeId; _context.SaveChanges(); }
To
[HttpPost] public CustomerDto CreateCustomer(CustomerDto customerDto) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest); var customer = Mapper.Map<CustomerDto, Customer>(customerDto); _context.Customers.Add(customer); _context.SaveChanges();
//we want to add this id in our DTO and return it to our client. customerDto.Id = customer.Id; return customerDto; } [HttpPut] public void UpdateCustomer(int id, CustomerDto customerDto) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest); var customerInDb = _context.Customers.SingleOrDefault(c => c.Id == id); if(customerInDb ==null) throw new HttpResponseException(HttpStatusCode.NotFound); Mapper.Map<CustomerDto, Customer>(customerDto, customerInDb); customerInDb.Name = customer.Name; customerInDb.Birthdate = customer.Birthdate; customerInDb.IsSubscribedToNewsletter = customer.IsSubscribedToNewsletter; customerInDb.MembershipTypeId = customer.MembershipTypeId;
_context.SaveChanges(); }
As <CustomerDto, Customer> is greyed out this portion can now be deleted once initially typed Visual Studio will now know what it expects and once grey it can be removed.
All the other customerInDb lines can be deleted
Using Camel NotationHow to configure Web API to return JSON objects in camel notation
Look at the results of the API POST Man to get the list of Customers. These are pascal notation, all the first letters are upper case.
As you can see the Pascal notation has a capital letter for the first letter of the word Id, Name, IsSubscribedToNewsletter etc.
In JavaScript we use CamelCase, the first letter of the first word is lowercase.1. In Solution explorer, open App_Start/WebApiConfig.cs2. WebApiFramework has a default routing rule, there is no Action see highlighted below.
public static void Register(HttpConfiguration config) {
var settings = config.Formatters.JsonFormatter.SerializerSettings;settings.ContractResolver = new CamelCasePropertyNamesContractResolver();settings.Formatting = Newtonsoft.Json.Formatting.Indented;config.MapHttpAttributeRoutes();config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}", //No Action is present
defaults: new { id = RouteParameter.Optional } ); }
Now you can see that we now have Camel Notation with lower case starting letters:
IHttpActionResultFor Restful convention, when we create a customer we want the create Status Code to be 201, to do this we must use IHttpActionResult as the return type. The Status codes response of 200 is the current response in Postman, however in Restful convention the response should be 201 when we create a new Customer. We need more control.
In Customers Controller we are going to change the return type from CustomerDto to IHttpActionResult. IHttpActionResult is similar to ActionResult in MVC Framework. It is implemented by a number of classes.
From
//POST /api/customers //to return the resource. //Post request specified [HttpPost] public CustomerDto CreateCustomer(CustomerDto customerDto) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
var customer = Mapper.Map<CustomerDto, Customer>(customerDto); _context.Customers.Add(customer); _context.SaveChanges(); //we want to add this id in our DTO and return it to our client. customerDto.Id = customer.Id; return customerDto; }
To
//POST /api/customers //to return the resource. //Post request specified [HttpPost] public IHttpActionResult CreateCustomer(CustomerDto customerDto) { if (!ModelState.IsValid) return BadRequest(); var customer = Mapper.Map<CustomerDto, Customer>(customerDto); _context.Customers.Add(customer); _context.SaveChanges(); //we want to add this id in our DTO and return it to our client. customerDto.Id = customer.Id; //return the URI Unified Resource Identifier /api/customers/10 return Created(new Uri(Request.RequestUri + "/" +customer.Id),customerDto); }
Before
After
Now add the same to the Get Customer method
From
//Get /api/customers/1 public Customer GetCustomer(int id) { var customer = _context.Customers.SingleOrDefault(c => c.Id == id); if (customer == null) throw new HttpResponseException(HttpStatusCode.NotFound); return customer; }
To
//Get /api/customers/1 public IHttpActionResult GetCustomer(int id) { var customer = _context.Customers.SingleOrDefault(c => c.Id == id); if (customer == null) return NotFound(); return Ok(Mapper.Map<Customer, CustomerDto>(customer)); }
ExerciseImplement API for CRUD operations for Movies, we will have different End points for getting all movies, getting a single movie by ID, end points for adding, updating and deleting a Movie.
We will get an exception when updating a movie.
Property id is part of the object key information and cannot be modified.
I have attached a PDF on how to resolve this issue.
Starting the WebAPI for Movies. – Up to Video 4 Building an API @5:15s
Building Movie APIusing System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Http;using System.Web.Http;using Vidly3.Models; namespace Vidly3.Controllers.Api{ public class MoviesController : ApiController { public MoviesController() { _context = new ApplicationDbContext(); } private ApplicationDbContext _context; //GET /api/movies public IEnumerable<Movie> GetMovies() { return _context.Movies.ToList(); } //GET /api/movies/1 public Movie GetMovie(int id) { var movie = _context.Movies.SingleOrDefault(c => c.Id == id);
if (movie == null) throw new HttpResponseException(HttpStatusCode.NotFound); return movie; } //POST /api/movies [HttpPost] public Movie CreateMovie(Movie movie) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest); _context.Movies.Add(movie); _context.SaveChanges(); return movie; } //PUT /api/movies/1 [HttpPut] public void UpdateMovie(int id, Movie movie) { if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest); var movieInDb = _context.Movies.SingleOrDefault(m => m.Id == id); if (movieInDb == null) throw new HttpResponseException(HttpStatusCode.NotFound); movieInDb.Name = movie.Name; movieInDb.ReleaseDate = movie.ReleaseDate; movieInDb.DateAdded = movie.DateAdded; movieInDb.NumberInStock = movie.NumberInStock; movieInDb.GenreId = movie.GenreId; _context.SaveChanges(); } //DELETE /api/movies/1 public void DeleteMovie(int id) { var movieInDb = _context.Movies.SingleOrDefault(m => m.Id == id); if (movieInDb == null) throw new HttpResponseException(HttpStatusCode.NotFound); _context.Movies.Remove(movieInDb); _context.SaveChanges(); }
}}
Testing Movies APIAgain, there is no ContentType and when navigate to http://localhost:57416/api/movies and inspect we can see that there is no ContentType
Open Postman and paste in the URL: http://localhost:57416/api/movies
Select a movie and Copy
SummaryWe learnt about Postman, Data Transfer Object, Auto Mapper, Action Results, Camel Notation
In the next section we will be focusing to Client-Side development, I will show you how to consume these API’s we have built and implement a few nice features in our application.
Section 5 Client-Side DevelopmentWe will be shifting to Client-Side Development
We are going to use jQuery ajax to consume the API’s built in the last section
jQuery plugins
1. Bootbox for Bootstrap dialog boxes2. Data Tables to display tables that have built in support for pagination, searching and sorting
TaskWe are going to extend our customer list and add a delete link in front of each customer. When the user clicks the link, we will use jQuery to call our API, and then remove this customer from the list.In Solution Explorer go to Views -> Customers -> Index. Add a new table heading with ‘th’, called Delete.
<table id="customers" class="table table-bordered table-hover"> <thead> <tr> <th>Customer</th>
<td>Membership Type</td> <td>Discount Rate</td> <td>Delete</td> </tr> </thead>And then when rendering a customer in a button. We should use hyperlinks, in this case we will use a button and then use a class=btn-link to make it look like a hyperlink.
<tbody>
@foreach (var customer in Model) { <tr> <td>@Html.ActionLink(customer.Name, "Edit", "Customers", new { id = customer.Id }, null)</td> <td>@customer.MembershipType.Name</td> <td>@customer.MembershipType.DiscountRate%</td> <td> <button data-customer-id="@customer.Id" class="btn-link js-delete">Delete</button> </td>
Give the table an id of customers.
<table id="customers" class="table table-bordered table-hover">
Give the button a second class js-delete
<td> <button data-customer-id="@customer.Id"
class="btn-link js-delete">Delete</button></td>
Now jQuery to handle the click events of the button. Scroll to the bottom of the file and create a scripts section.
@section scripts{ <script> $(document).ready(function () { // Standard jQuery document ready Start $("#customers .js-delete").on("click", function () { var button = $(this); if (confirm("Are you sure you want to delete this customer?")) { $.ajax({ url: "/api/customers/" + $(this).attr("data-customer-id"), method: "DELETE", success: function () { console.log("Success") button.parents("tr").remove(); }
}) } }); }); </script> }