bjb365
3/17/2015 - 8:18 PM

ASP.NET

ASP.NET

# User Controls
.ascx
Doesn't seem to exists in MVC, where the convention is to use 
@Html.RenderPartial("[View name]")
- or -
@Html.Partial("[View Name]")


The tilda means "root of application"
~/Views would be the View folder in teh root of the application


# Decorators 
# Restrict access to method or controller
[Authorize Roles() ]


# Errors
FilterConfig.cs 
Registers a new HandleErrorAttribute()

Web.config
configuration > system.web >
  <customError mode="On,Off,RemoteOnly> # RemoteOnly is default. This means requests from the localhost will show errors

The Error view that is shown in Views/Shared/Error.cshtml 


You can create your own "Filters" ( decorators ) by creating a class in the Filters folder and inheriting from "ActionFilterAttribute" 

------------------------------------------------------------------------------------------

MVC Runtime has more than one view rendering engine :
Webforms ( .aspx, ascx  )
Razor ( .cshtml )

When creating a view in VS
- you can have a strongly typed view wherein you tell it exactly what type of model it will be using
- Scaffold template
  - Scaffolding will reflect upon the model object and create a view based on those properties of your model

In the view at the top, you tell it what model it is using :
@model IEnumerable<Fully.Qualified.ModelName>

In the view, you can access the model that you passed like this :
@Model.MyModelProperty

// Comment Tags :
@* @Html.CommentedOutCode *@

// Raw output
@Html.Raw(PropertyToNotBeHtmlEncoded)

// Display x / 10 ( implicit expression )
@item.Rating / 10 

// Display the rating divided by 10. This is explicit expression
@(item.Rating / 10)

//Escape at sign
@@

// foreach loop 
@foreach(var item in Model) 
{
  <h1>@item.MyProperty</h1>
}

// Literal Text - edge case. This likely wont be needed
@:Literal Text


------- LAYOUTS -------------------------------------

~/Views/_ViewStart.cshtml
- defines the layout to be used 

# Default Layout in MVC Application 
~/Views/Shared/_Layout.cshtml

Views can set their own layout in a code block 

@{
  Layout = "MyLayout"
}

Layouts have to special methods 
@RenderBody
@RenderSection("section name", required:[true, false])

The @RenderSection will render out a section declared in a view
@section MySection {
  // Html / razor expressions here
  
}



-- HTML HELPER -------------------
Creates links, forms, validation messages, inputs etc. 
@Html.xxx

'Html' is a property of the ViewPage base class

# Example
@Html.ActionLink("Edit", "Edit", new { id = Item.Id })
# This will be passed through the routing engine to help create the link

# writes an <form> tag
@Html.BeginForm()
- is wrapped in a using statement:
  @using (Html.BeginForm()){
    ...
  }

# a hidden input 
@Html.HiddenFor()

# <label>
@Html.LabelFor( model => mode.City)

# Will determine what type of input to use ( input type="text", input type="tel" etc. ) 
@Html.EditorFor()

@Url.Action("Index") <- renders a link 


# WHen user clicks the Save action from a form
--- Model Binding
public ActionResult Edit(int id, FormCollection collection) 
{
  var review = _reviews.Single( r => r.Id = id );
  if(TryUpdateModel(review)) // Goes through a Model Binding process 
  {
   // Save into db
  }
}


---------- PARTIAL VIEW 
Reusable view
When creating a partial view, in VS clicking the partial view checkbox just changes how the code is generated

@Html.Partial("[Name of view]", [model]) // No need to add the cshtml extension
// If the Partial is in the "Shared" folder, it will be availble everywhere

Adding a Model to the _Layout.cshtml view is bad practice

// Set up a "Subrequest" within another view
@Html.Action("BestReview", "Reviews",...)
// Where BestReview is the Action, and Reviews is the Controller
// You may also want to decorate the "BestReview" method with the [ChildActionOnly] decorator
The Controller Method "BestReview" will return PartialView("_ViewName", [model]);








----------------------------------------------------------------------------------------------------------------
-- LINQ  -----------
Example :
var model = 
  from r in _reviews
  orderby r.Country
  select r;
  
In this example, _review is a List<Restaurants> where Restaurants is a Plain old C# obj.

# Return a single object from the List that matches the linq query 
var review = _reviews.Single( r => r.Id == 123 )


-------------------------------------------------------------
## Entities Module ####################################

Create your POCOs

Create a class that inherits from DbContext ( ex. name OdeToFoodDb ) 
- has properties of DbSet<Object> 

Anythin that implements the IDispose method should be used to clean up stuff.
In the example, in the Controller that was using the OdeToFoodDb, the Dispose() method was overridden and Dispose() was called on the OdeToFoodDb ivar

// This will retrieve all records from teh Retaurants table
var model = _db.Restaurants.ToList() 

Create DB In VS
View > Database Explorer > Add Connection
Server name: (Localdb)\v11.0   // This is a sql server localdb. for developers. Launched on demand.

-- Database Migrations --
You can configure your database with a connection string in the DbContext class
Create a constructor and pass to the base() the conn. string. 
public OdeToFoodDb() : base ("server=prod;initial catalog=mydb; integrated security=true")
This is not normal. we don't want to hardcode the conn string.

Web.config 
  <connectionStrings>
    <add name="DefaultConnection" ... >
    
Now, when calling  into the DbContext constructor, pass the web.config connection string name into the base constructor
public OdeToFoodDb() : base ("name=DefaultConnection")

In V.S. the App_Data folder will show the .mdf file for your DB ( you have to click on teh Show All Files toolbar icon )

## Managing DB Migrations ##
Open Package manager console
PM> Enable-Migrations -ContextTypeName OdeToFoodDb

This will create a "Migrations" folder with some C# code in it. 
Migrations/Configurations.cs
- AutomaticMigrationsEnabled = [true,false]; allows C# to make database changes
- Seed() method. Lets you populate tables with data 
-- AN EXAMPLE OF CODE FOR THIS METHOD IS AT 07:05 of the 4th Chapter in "Database Migrations" section
- context.Restaurants.AddOrUpdate( r => r.Name, new ... ) method is used to add the data.

// Use PM to run the Seed method to get that data added/updated:
PM> Update-Database -Verbose 


--------------------------------------------------
-- Using LINQ 

2 forms of LINQ you can write. One is used earlier in the notes where it looks just like SQL ( from r Where ... Orderby ... etc. ) 
This is call ComprehensionQuery Syntax

The second form :
var query = _db.Restaurants
  .Where(r => r.Country == "USA")
  .OrderBy( r => r.Name )
  .Skip(10)
  .Take(10);

Skip() and Take() are great for paging

-- Download LINQPAD for testing and exploring Linq queries

:: lots of talk about ordering using C.Q. Syntax ::

"a Projection" / "View Model" 
- when handing back the Restaurant model, we may want to add the Number of reviews.. Heres how : 
var model = from r in _db.Restaurants orderby ... 
  select new {
    r.Id, r.Name, r.City, r.Country, NumberOfReviews = r.Reviews.Count()
  }
This select is an anonymously typed object. So we create a "View Model" which includes the properties added in the select new {} object above
Now change the select new {} to select new MyViewMoel {} 

We can use AutoMapper to accomplish the above example


---- FILTERING Our Linq --------
In our controller method, we will have "string searchTerm" as a method parameter

in the Linq query :
  .Where( r => searchTerm == null || r.Name.StartsWith(searchTerm)
  
Back into Razor, we can add a <form> so the user can pass in "?searchTerm"

:: create a <form> <input text> <input button>



---------------------------------------------------------------------
## Data part 2 of 2  ################################################

Create a new controller "RestaurantController" and use the MVC Controller with read/write...using Entity Framework
Tell VS what model and db context we are using and we have generated code 

:: Without writing any code, this controller could Add, Update & Delete a record ::

In an example where Restaurants are listed, each Restaurant has a link to Reviews.
In the ReviewController's Index() method, the restaurantId should be passed. Typically in MVC, the id being passed would be expected to be a Review ID, but thats not what we want. 
public ActionResult Index([Bind(Prefix="id")] int restaurantId ){ ... }
This tells the MVC Model Binder that when it looks for the reatuarantId look for something called id ???????
So the URL will be passing ?id=123, but the parameter name in the method will be "restaurantId" 

In the view that was created for this Controller, the @model is Restaurant.
A Partial is created "_Review.cshtml" where the @model is RestaurantReview

In the Review's Index.cshtml, we then use @Html.Partial("_Reviews", @Model.Reviews) to call the Reviews partial and pass in the model

With this set up, there will be an error. The @Model.Reviews passed was null. 
In the Restaurant Model class, the ICollection<Restaurant> ivar is not populated by E.Framework because it is of another table.. 
Solutions: 
 - add the 'virtual' keyword to the ICollection<Restauarnt> ivar ( This is lazy loading. There are 2 queries. The second query gets reviews when it is accessed in code  )
- :: I'm guessing that there are some decorators that could also be used instead of usign virtual keyword ::


 Editing an object in the Database.
 In the public ActionResult Edit(RestaurantReview review) method ...
 _db.Entry(review).State = EntityState.Modified;
 _db.SaveChanges(); 
 
 Security with the MVC Model Binder. It will try to add everything to the model that it can from the request. 
 This is called "Overposting" or "Mass assignment" 
 A Quick Solution,, add Bind decorator and set the "Exclude" param
 public ActionResult Edit([Bind(Exclude="ReviewrName")] RestaurantReview review)
 
 Another solution is to use a new View Model that is only for Editing.
 
 -- VALIDATION ANNOTATIONS 
 In the RestaurantReview model, decorate the ivars 
 [Range(1,10)] - only allows an int to be within 1 and 10 
 [Required] - obvious what it is
 [StringLength(1024)] - sting must be < 1024 characters 
 
 Right off the bat there is an error. The decorators we added in the model changes the database schema
 Solution : 
 PM> Update-Database -Verbose -Force
 
The View's @Html.EditorFor() method will automatically add javascript for client side validation now that we have updated our Model with the decorators from above. 
The error message is outputted with @Html.ValidationMessageFor()

In the Model class
[Display(Name="Good Display Name")] 
[DisplayFormat(NullDisplayText="anonymous")]


-- CUSTOM VALIDATION 
Write a custom Attribute/Decorator 
Derive from base class "ValidationAttribute" 
override the IsValid() method 

All Validation attributes ( including built in attrs) can haev a custom error message

In addition to using these Attributes, you can have your Model Class implement the IValidateObject interface


-----------------------------------------------------------------------------------

-- Scripts/Styles

Bundles-
  ScriptBundle
  StyleBundle 
In the BundleConfig, you can register "bundles" and declare what goes in them. Then, in your views, you can render those bundles out

::BundleConfig 
public static void RegisterBundles(BundleCollection bundles){
  bundles.Add( new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jaquery-{version}.js", "~/Scripts/more_stuff.js")
  ...
}

The program is smart enoguh to pick out the {version}. so if we replace jquery-1.7.js in the scripts folder with jquery-1.8.js, it is smart enoguh to pick up the new version.
Wild cards in the file names are acceptable 

We then render the scripts to the browser using 
@Scripts.Render("~/bundles/jquery") ( or @Styles.Render(...)) 

Debug vs Release mode will cause the Bundles to be rendered differently. Release will combine and minifiy scripts & styles.

--- AJAX 
:: the presented added 1000 Restaurants to the DB and talks about how ajax would be great for searching now 

This is added to the partials so that scripts can be rendered in the "Layout" view.
- Partial :
@section Scripts {
  @Scripts.Render("~/bundels/jquery")
}
- Layout View :
@RenderSection("scripts", required: false)

-- AJAX HELPERS IMPLEMENTATION  >>>> 
// Now instead of using the @Html.BeginForm, we use @Ajax.BeginForm
@using( Ajax.BeginForm(
  new AjaxOptions {
    HttpMethod = "get",
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId="restaurantList}))
{
  <input type="search" name="searchTerm" />
  <input type="submit" value="Search By Name" />
}
@Html.Partial("_Restaurants", Model) // Where _Restaurants.cshtml displays the list of restaurants. 

We then need to wrap the content that is being updated with a div#restaurantList

In the Controller Method ( where we are selecting the list of restaurants ) add in this condition before teh return View(model):

if( Request.isAjaxRequest()) {
  return PartialView("_Restaurants", model);
}

return View(model);


----- An Async Search 
:: Presenter writes javascript to post form with ajax

------- Autocompletion

public ActionResult Autocomplete(string term) {
  var model = _db.Restaurants
    .Where(..)
    .Take(10)
    .Select( r +=> new { label = r.Name }); // The jquery UI Spec requires that the JSON being passed back has "label", "value" or both as the keys
  
  return Json(model, JsonRequestBehavior.AllowGet);
}

in the view :
<form>
<input type="search" data-autocomplete="@Url.Action("Autocomplete")" /> ( The URL Helper generates a URL for the HomeController's Autocomplete Action

:: The rest is finished in Javascript and uses Jquery UI's $.autocomplete() function


------- Paging Results 
:: Presenter uses NuGet to download "PagedList.Mvc" package ( once installed they are in the References folder of the project ) 

In the _db.Restaurants.Where().OrderBy().Select() we remove .Take() and add .ToPageList(page, 10) // Page #, records per page

The View's @model is updated to be typed with IPagedList<RetaurantsListViewModel> // Note that this is not a fully qualified type
In the Web.Config in teh "Views" folder ( not the one in teh root ) we can add namespaces
configuration > system.web.webPages.razor > pages 
  <namespaces><add namespace="OdeToFood.Models" />

The _Restauarnats.cshtml has its @model updated 
We then add:
<div>@Html.PagedListPager(Model, page => Url.Action("Index", new { page }), PagedListRenderOptions.MinimalWithItemCoutnText)
// The lambda expression says "given a page, returns a URL to go to that page. The Index action then passes page to the request.

:: The PagedList css is added the the StyleBundle in BundleConfig.cs to make it look better

For Ajaxing the Pageing links:
:: the presenter adds a click event handler on the <section> item that is wrapping the content that is updated with Ajax. 
$('.main-content').on("click", ".pagedList a", getPage); // This attaches the click handler to section, but only routes the click requests that originate from elements matching ".pagedList a"


----------------------------------------------------------------------------------------------------------------
Security / Authentication

By default there is an AccountController & View created by VS

/Filters/InitializeSimpleMembershipAttribute.cs
- A bunch of this code is not used .. zero in on the WebSecurity.InitializeDatabaseConnection() method call 
- Cut this line out and move it into the Application_Start() method in the Global.ascx.cs file 

- In the Models folder there is a "UserContext" class in the AccountModels.cs file
This will give the AccountControllers access to the DB Context

Create  a UserProfile model object. 
[Table("userProfile")] // on class. denotes which db table to use
[Key] // on property sets it as a primary key
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] // sets the field as an auto-inc identity

Add this new Model ( "UserProfile" ) to our DbContext class "OdeToFoodDb":
public DbSet<UserProfiles> { get;set;}