ASP.Net

ASP.Net is a web framework for server-side web applications, producing dynamic web pages.

ASP stands for Active Server Pages.

ASP.Net is a big improvement on the previous ASP. The main difference to the pages is that ASP pages are interpreted (like PHP) while ASP.Net pages are compiled.

The main assembly for ASP.Net MVC is System.Web.Mvc.

ASP.Net Frameworks

ASP.Net is made up of three frameworks. You can use a single framework, or multiple together. A common combination is MVC with Razor Web Pages. Or create an MVC site, with Web Forms used for the data access portion due to its library set.

Web Forms

Use *.aspx pages.
Mimics Win Forms apps.
Focuses on declarative and control-based programming.
Not so good at Separation of Concerns or automated unit testing.

Includes a WYSIWYG drag-n-drop interface.
Includes an event model to program against.

Will render the HTML for you (which does lessen your control of the rendered HTML).

Automatic preservation of state between HTTP requests.

Generally there is one file per URL, rather than the routing that MVC uses.

MVC

Based on the Model-View-Controller design pattern.
Good for Test Driven Development, Separation of Concerns, Inversion of Control, and Dependency Injection.
Good for separating the business logic layer from the presentation layer.

Does not automatically preserve state between HTTP requests.

Provides greater control of the rendered HTML than Web Forms does.

Uses routing instead of having one web page per URL.

Web Pages

Uses *.cshtml pages.
For very simple designs, similar to PHP. Creates HTML pages with integrated server-side code using the Razor language.

Features Helper functions to render common HTML controls (such as maps, twitter feeds, etc).

Easy to add Javascript to.

Getting Started

Setup

Setting up a basic environment to create and run an ASP.Net site with a database backend.

Install:
1) Visual Studio: for writing and testing code
2) SQL Server: database
3) IIS Express: local web server to run application

Run

Start Coding:
1) Open Visual Studio (these instructions written for VS Community 2015)
2) Click New Project > C# > Web > ASP.Net Web Application > MVC Template
- Check "Add Unit Tests" (use default name MyAppName.Tests)
- Leave "Authentication" on "Individual User Accounts"
- Uncheck Azure "Host in the Cloud"
3) Click "Ok" and wait for template to generate
4) Click "Play"
- IIS Express will be started automatically to run the application
- The default landing page will open in the browser

The IIS Express icon will be visible on the right-side of the task bar, looking like a stack of blue boxes. Right-click on it to get to a list of all running applications.
Requests from the browser come to IIS Express, which sends them to the ASP.Net Routing Engine.

HTML Basics

With MVC 4 and later, the rendered web pages will all include basic HTML5 best-practices.


  <!DOCTYPE html> <!-- HTML 5 -->
  <html lang="en"> <!-- spoken language -->
    <head>
      <meta charset="utf-8" /> <!-- for cross-browser compatibility -->
      <meta name="viewport" content="width=device-width" /> <!-- for mobile device compatibility -->
      <script src="/Scripts/modernizr-2.6.2.js"></script> <!-- backwards compatibility to pre-HTML5 browsers -->
    </head>
  </html>

AutoEventWireup

Auto event wireup is a process ASP.Net runs that searches for certain method signatures, and automatically sets those methods to be called when certain events run.

If more that one method signature matches an event, only the last method found will be attached to the event.

For instance, in MyProject/Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    protected void Application_OnStart()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}
Both of these method signatures maps to the same Application Start event. Whichever order they are defined in, only the last one will be run on the Application Start event.

Application Life Cycle

The application life cycle goes from when the application starts running on IIS, to when it stops running on IIS.

(Unfortunately, many online articles use this term as a synonym for the page/request life cycle.)

Pre Application Start Event

(This topic is applicable to any .Net assembly, not just ASP.Net MVC.)

Any .Net assembly can specify a method to run after the assembly has been loaded, and before any other code in the assembly is run.

If multiple assemblies have a pre-start event, there is no guarantee in what order they will be run.

Specify the pre-start method in MyProject/Properties/AssemblyInfo.cs

[assembly: PreApplicationStartMethod(typeof(Fully.Qualified.MyType), "MethodName")]

Specify the pre-start logic:

namespace Fully.Qualified
{
    internal static class MyType
    {
        internal static void MethodName()
        {
            //pre-start logic
        }
    }
}

Application Start Event

Occurs when the application receives its first request.

This is the very (almost) first event, so this is where global configuration is run.
Ex: registering Areas, Filters, Routes, and Bundles.

MyProject/Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

Application End Event

Occurs when the application is stopped in IIS.
The event may not fire if the application crashes.

MyProject/Global.asax:

namespace MyNamespace
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_End()
        {
            //ending logic
        }
    }
}

This event will be fired when IIS pool is restarted, and when the application is restarted. It will also be fired when the application domain is reloaded (i.e for example when you change the web.config) or the application is reloaded into a different application domain.

Request Life Cycle

The events that occur every time an HTTP request is handled by the application.

Overview

Request is received

Routing

    URL Routing Module
    MVC Route Handler
    MVC HTTP Handler

Controller Initialization

    Controller Factory
    Activator
    Dependency Resolution

Action Execution

    Model Binding
    Action Filters
    Action Execution
    Post Action Filters
    Action Result

Result Execution

    Result Filter
    Invoke Action Result
        if ViewResult type: View Engine, Find View, Render View
    Post Result Filters

Response is returned

Events

These events are available to any ASP.Net framework.
The events with "Post" prefix will run after that event occurs. The events with "Pre" or no prefix will (mostly) run before that event occurs.

Application_Start
    only runs on the first request
HttpApplication.Init
    only runs when a new HttpApplication is instantiated
IHttpModule.Init
    only runs when a new HttpApplication is instantiated
    runs for each custom HttpModule registered in Web.config

BeginRequest

AuthenticateRequest
    occurs after the request has been authenticated
PostAuthenticateRequest

AuthorizeRequest
    occurs after the request has been authorized
PostAuthorizeRequest

ResolveRequestCache
    if the request can be fulfilled from cache
    then the result is returned and the rest of the process is skipped
PostResolveRequestCache
    occurs only if the request is fulfilled from cache
    this is the event the URL Routing Module listens for

MapRequestHandler
    selects a handler to fulfill the request
    usually uses an MvcHandler
PostMapRequestHandler

AcquireRequestState
    acquires state data, such as session state
PostAcquireRequestState

PreRequestHandlerExecute
RequestHandlerExecute
---    all your MVC code executes between these events ---
PostRequestHandlerExecute

ReleaseRequestState
    causes state modules to save the current state
PostReleaseRequestState
    after this event, any Response Filters will be run

UpdateRequestCache
    lets caching modules store responses for later use
PostUpdateRequestCache

LogRequest
    performs logging
PostLogRequest

EndRequest
    raised when the CompleteRequest method is called
    
Request Handler Details

Between RequestHandlerExecute and PostRequestHandlerExecute, all your MVC code gets run.
Here are more details of what runs between those events.

[Diagram]

HttpApplication.BeginProcessRequest

IControllerFactory.CreateController (called by the MvcHandler)
Controller Constructor
Controller.BeginExecute (can be overriden)
Controller.Initialize (can be overriden, but still needs to call the base)
Controller.BeginExecuteCore
Controller.CreateTempDataProvider (can be overriden)
Controller.CreateActionInvoker (can be overriden)

Authentication Filters invoked
Authorization Filters invoked

Model Binding to parameters

Controller.OnActionExecuting
each Action Filter is executed
if action is async, it is run here
Controller.EndExecute
Controller.EndExecuteCore

HttpApplication.EndProcessRequest
    if action is synchronous, it is run here

each Post Action Filter is executed
Controller.OnActionExecuted

each Authentication Filters is run
    Controller.OnAuthenticationChallenge
    AuthenticationFilter.OnAuthenticationChallenge
    
results are executed
    Controller.OnResultExecuting
    ResultFilter.OnResultExecuting
    action result is invoked
        for instance, find view and render it
    ResultFilter.OnResultExecuted
    Controller.OnResultExecuted
    
Controller.Dispose

Versions

MapRequestHandler, LogRequest, and PostLogRequest are newer events.
They require:
- IIS 7.0 Integrated Mode (or a later version of IIS)
- .Net Framework 3.0 or later

HttpApplication

Instances of HttpApplication are only created by the ASP.Net infrastructure.

One instance of HttpApplication can only process on Http Request at a time.
One instance of HttpApplication may be used to process multiple Http Requests over time.

You can override the HttpApplication.Init method in Global.asax. This will run each time an HttpApplication is instantiated.

public class MvcApplication : System.Web.HttpApplication
{
    public override void Init()
    {
        //initialization code
    }
}
Modules

About

The MVC life cycle is a series of events that Modules can listen for.

You can write your own Modules to add/change functionality. Modules are designed to respond to MVC life cycle events, thus becoming part of the life cycle.


class MyModule : IHTTPModule { }

Modules frequently read and edit the HTTP Context object. Uses include logging, authentication, or fast redirects.

Most of what your custom HTTP Module can achieve can also be done through the Global.aspx file. But if your separate the functionality into a Module, you can reuse it in other applications.

You can register Modules through code (like in the PreApplicationStart event) or in a configuration file.

Custom Modules

Create a custom module and hook it into the Request Life Cycle events.

Custom module (MyProject/App_Code/CustomHttpModule.cs):

using System;
using System.Web;

namespace Samples.AspNet.CS
{
    public class CustomHTTPModule : IHttpModule
    {
        public CustomHTTPModule()
        {
            // Class constructor.
        }

        public void Init(HttpApplication app)
        {
            //attaches this module to request life cycle events
            app.AcquireRequestState += new EventHandler(app_AcquireRequestState);
            app.PostAcquireRequestState += new EventHandler(app_PostAcquireRequestState);
        }

        public void Dispose()
        {
            // Add code to clean up the instance variables of a module.
        }

        public void app_AcquireRequestState(object o, EventArgs ea)
        {
            HttpApplication httpApp = (HttpApplication)o;
            HttpContext ctx = HttpContext.Current;
            ctx.Response.Write(" Executing AcquireRequestState ");
        }

        public void app_PostAcquireRequestState(object o, EventArgs ea)
        {
            HttpApplication httpApp = (HttpApplication)o;
            HttpContext ctx = HttpContext.Current;
            ctx.Response.Write(" Executing PostAcquireRequestState ");
        }
    }
}

Register Module

Custom module registration (IIS 7.0 Classic Mode, or earlier IIS version):
MyProject/Web.config:

    <configuration>  
        <system.web>  
            <httpModules>  
                <add type="Samples.AspNet.CS.CustomHTTPModule" name="CustomHttpModule" />  
            </httpModules>  
        </system.web>  
    </configuration>  

Custom module registration (IIS 7.0 Integrated Mode, or later IIS version):
MyProject/Web.config:

    <configuration>  
        <system.webServer>  
            <modules>  
                <add type="Samples.AspNet.CS.CustomHTTPModule" name="CustomHttpModule" />  
            </modules>  
        </system.webServer>  
    </configuration>  
Routing

Routing lets you use URLs that do not map to specific files in a web site. Thus, URLs can be more descriptive of actions and be more easily understood by users.

Default routing: websiteUrl/ControllerName/ActionName/parameters
You do not need to manually register this default routing behavior.

You can inspect route handling data within a controller action by looking at the global "RouteData" object.

Route Handler

A route handler simply returns an HTTP handler.


class MyRouteHandler : IRouteHandler { }

//the default is MVCRouteHandler

Convention Based Routing

Project/Global.asax.cs > Application_Start method is run once when the application starts.
It calls

RouteConfig.RegisterRoutes(RouteTable.Routes);
which contains all code for registering routes.
To register custom routes, add to Project/App_Start/RouteConfig.cs > RegisterRoutes().

How to register a route:

routes.MapRoute(name: "Default", //internal name of route mapper
    url: "{controller}/{action}/{id}", //pattern of route
    defaults: new { controller="Home", action="Index", id=UrlParameter.Optional } ); //defaults fill in missing information
Each route needs an associated RouteHandler class (which is done automatically by MapRoute). The default is MVCRouteHandler. The RouteHandler will create the HTTPHandler that will actually process the request.

Route mappers will be used in the order in which they are registered. The first route mapper that matches a url will be used.

Routes are case-insensitive.

Specify a route parameter is optional, without setting a default value:

defaults: new { controller="Home", action="Index", id=UrlParameter.Optional }

Ignore

This default ignore in Project/App_Start/RouteConfig.cs

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
means that urls to specific real files will be ignored by the routing engine, they'll be handled like normal web requests.

Attribute Routing

Attribute routing lets you define routes at the Action level. This was added in MVC 5.

It is considered easier to read, because the route(s) is right next to the Action is points at. My concern is that I cannot see all my routes at once, to check that they don't override each other.

To enable attribute routing:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        //ignore convention based routing
        routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);

        //enable attribute routing
        routes.MapMvcAttributeRoutes();
    }
}

Applying route to Action:

[Route(“{productId:int}/{productTitle}”)]
public ActionResult Show(int productId) { … }

Applying route prefix to Controller and Actions:

//set prefix for all Action routes
[RoutePrefix("reviews")]
public class ReviewsController : Controller
{
    // Catches "/reviews"
    [Route]
    public ActionResult Index() { … }

    // Catches "/reviews/5"
    [Route("{reviewId}")]
    public ActionResult Show(int reviewId) { … }
    
    // Catches "/spotlight-review" because ~ overrides the Controller prefix
    [Route("~/spotlight-review")]
    public ActionResult ShowSpotlight() { … } 
}

Applying default route to Controller:

[RoutePrefix("promotions")]
[Route("{action=index}")]
public class ReviewsController : Controller
{
    // Catches "/promotions"
    public ActionResult Index() { … }

    // Catches "/promotions/archive"
    public ActionResult Archive() { … } 
    
    // Catches "/promotions/edit/5"
    [Route("edit/{promoId:int}")]
    public ActionResult Edit(int promoId) { … } 
}

Registering a Controller with an Area without the AreaRegistration class:

[RouteArea("Admin")]
public class MenuController : IController
{
}

//register a custom prefix for the area at the same time
[RouteArea("BackOffice", AreaPrefix = "back-office")]
public class OtherController : IController
{
}

Routing constraints are written after the "parameterName:".
Example of routing based on data type:

// Catches "/users/5"
[Route("users/{id:int}"]
public ActionResult GetUserById(int id) { … }

// Catches "users/ken"
[Route("users/{name}"]
public ActionResult GetUserByName(string name) { … }
Notes that the "int" is listed first because everything is coming in as a string, so that's not a good filter. Routing is checked from the top of the file down.

You can apply multiple constraints separated by ":"

[Route("users/{id:int:min(1)}")]

Constraints:
- {x:alpha} matches one char [a-z,A-Z]
- {x:length(10)} matches a string with length 10
- {x:length(10,20) matches a string with length 10 to 20 inclusive
- {x:minlength(10)} matches a string with length 10 or more
- {x:maxlength(10)} matches a string with length up to 10
- {x:regex(\d\d\d)} matches a string by regular expression (in this case, contains 3 consecutive digits)
- {x:decimal}
- {x:double}
- {x:float}
- {x:long}
- {x:int}
- {x:min(10)} matches an integer down to 10
- {x:max(10)} matches an integer up to 10
- {x:range(5,10)} matches an integer from 5 to 10 inclusive
- {x:bool} matches a boolean value
- {x:datetime}
- {x:GUID}

Custom Attribute Routing Constraints

Example constraint only accepts a few values, entered as "valueA|valueB|valueC":

public class ValuesConstraint : IRouteConstraint
{
    private readonly string[] validOptions;

    public ValuesConstraint(string options)
    {
        validOptions = options.Split('|');
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
        }
        return false;
    }
}

Register the custom constraint:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        var constraintsResolver = new DefaultInlineConstraintResolver();
        constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
        routes.MapMvcAttributeRoutes(constraintsResolver);
    }
}

Route Names

You can name a route, and use that name to generate the URL:

//in controller
[Route("menu", Name = "mainmenu")]
public ActionResult MainMenu() { … }

//in view
<a href="@Url.RouteUrl("mainmenu")">Main menu</a>

Ordering

You want to register routes from the most specific (to be checked first) to the least specific (defaults to use last).

Example of using both convention based routing and attribute routing at the same time:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);

    routes.MapMvcAttributeRoutes();

    AreaRegistration.RegisterAllAreas();

    routes.MapRoute(
        name: “Default”,
        url: “{controller}/{action}/{id}”,
        defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }
    );
}

HTTP Handlers


class MyHTTPHandler : IHTTPHandler { }

These handlers are responsible for actually generating a response to a request. Only one HTTP Handler can execute per request.

You usually do not need to make custom HTTP Handlers. Maybe if you need to handle an unusual file type. If so, you can register custom HTTP Handlers when registering routes - reference a Route Handler that returns your custom HTTP Handler.

The default handler is MVCHandler.

Your controller is initialized by:

    MVCHandler.ProcessRequest()
        MVCHandler.ProcessRequestInit()
            //gets Controller from ControllerFactory based on the supplied route
        Controller.Execute()

Controllers

Basic

A controller is a single class that is made up of public methods called "Actions".
HTTP requests will be routed to a particular controller based on the name of the class (pattern "XController", where "X" is used in the route).


class MyController : IController 
{
    public void Execute() { ... } //runs the Action Invoker
}

//you'll usually derive from MVC's abstract controller class
class MyController : Controller { }

Controller Factory


class MyControllerFactory : IControllerFactory { }
//you'll usually use the default DefaultControllerFactory

Scaffolding

When adding a new controller through Visual Studio, there are many template options that can generate boiler-plate code based on a Model and basic Create/View/Edit/Delete actions.

Actions

An "action" is a public method in a controller.
An action will handle an HTTP request and return a response.
An action is selected (mostly) based on the name of the method.

Action Invoker


class MyActionInvoker : IActionInvoker { }
//you'll usually use the default ControllerActionInvoker

The action invoker:
    selects the controller method that best matches the request
    runs authentication and authorization with Filters
    Model Binding
    Pre Action Filters
    Execute Method (an ActionResult is returned)
    Post Action Filters
    Pre Result Filters
    Execute Action Result
    Post Result Filters
    
Action Method Selection

1) Find all the controller methods with the same name as in the route
    Methods with incompatible Accept Verbs are not selected
    Methods must be public, non-static, and non-special (ex: not constructors)
1b) If only one method is possible, it is selected now
2) Now disregard methods with custom Action Selectors that return false
2b) If only one method is possible, it is selected now
2c) If only one possible method has Action Selectors, it is selected now
3) If there is more than one possible method still, an error is thrown (ambiguous call)

(note that the parameter list is not taken into account)

Child Action

A child action is a normal action that you intend to call from a View to render a partial view.

You have the option of making an Action into just a child action by marking it with the ChildActionOnly Attribute. These actions cannot be reached by URL.


//in controller
[ChildActionOnly]
public ActionResult MyAction()
{
    bool isChildAction = controllerContext.IsChildAction; //true when action is called from a View

    return PartialView();
}

//in view
@Html.Action(...)       //returns the results as a string
@Html.RenderAction(...) //renders results directly to the Response

Some sources say child action results are not cached, some say they are. So try that out.

Examples

Differentiating getting a page from submitting a form:

[HttpGet]
public ActionResult Edit(int id)
{
    var model = LoadModel(id);
    return View(model);
}

[HttpPost]
public ActionResult Edit(int id, FormCollection formData)
{
    var model = LoadModel(id);
    if(TryUpdateModel(model)) //uses model binding to update "model" with the formData
    {
        SaveModel(model);
        return RedirectToAction("Index");
    }
    return View(model); //update failed, let user try again
}
On a successful update, the user is returned to a page where they can view the update and decide on another action.
This also keeps the user from hitting "refresh" on the "submit form" action.

Basic validation and save:

using System.Data;

[HttpPost]
public ActionResult Create(MyModel model)
{
    if(ModelState.IsValid) //checks that model binding worked smoothly, and all data validations passed
    {
        _db.MyModels.Add(model);
        _db.SaveChanges();
        return RedirectToAction("Index", new { id = model.Id });
    }
    return View(model); //display the Create page again, with data already filled in and validation errors displayed
}

[HttpPost]
public ActionResult Edit(MyModel model)
{
    if(ModelState.IsValid)
    {
        _db.Entry(model).State = EntityState.Modified;
        _db.SaveChanged();
        return RedirectToAction("Index", new { id = model.Id });
    }
    return View(model);
}

Action Selector Attributes

Non Action

NonAction means the method is not an action to be selected by the Action Invoker.


[NonAction]
public Object MyMethod() { }

Action Name

ActionName sets the alias-name of the method matched against the route.


[ActionName("CustomName")]
public ActionResult MyMethod() { }

Accept Verbs

Accept Verbs specify which HTTP request methods can call this method.


//use shortcut attributes
[HttpPost] //only POST requests can access this method
[HttpGet]
[HttpPut]
[HttpDelete]
[HttpOptions]
[HttpPatch]

//or
[AcceptVerbs(HttpVerbs.Get|HttpVerbs.Post)]

(Accept Verbs used to be called Action Verbs)

Custom Action Selectors

You can create custom action selectors by inheriting from ActionMethodSelectorAttribute.


class MyCustomActionSelector : ActionMethodSelectorAttribute
{
    public bool IsValidForRequest(ControllerContext context, MethodInfo info)
    {
        //...
    }
}

//applied to controller method
[MyCustomActionSelector]
public ActionResult MyAction() { }

Example custom action selector with parameters in constructor

class MyCustomActionSelector : ActionMethodSelectorAttribute
{
    private int a;
    private string b;
    
    public MyCustomActionSelector(int a, string b)
    {
        this.a = a;
        this.b = b;
    }
    
    public bool IsValidForRequest(ControllerContext context, MethodInfo info)
    {
        //...
    }
}

//applied to controller method
[MyCustomActionSelector(5, "dog")]
public ActionResult MyAction() { }

Action Results

Return types from action methods.

Types

ViewResult: a view is rendered and returned
PartialViewResult: a partial view is rendered and returned
ContentResult: returns string literal
JsonResult: returns JSON-formatted string
JavaScriptResult: returns a script to execute
FileResult: returns a file
RedirectResult: redirects to another url
RedirectToRouteResult: redirects to another controller/action
EmptyResult: nothing is returned
HTTPUnauthorizedResult: returns HTTP 403 status

View Result Execution


return View();


return View(model);

Default Razor view engine search locations:
    ~/Views/{controller}/{action}.cshtml
    ~/Views/Shared/{action}.cshtml
    ~/Areas/{area}/Views/{controller}/{action}.cshtml
    ~/Areas/{area}/Shared/{action}.cshtml
The "conventional view" is one found in these locations.
    

return View("nonConventionalView", model);

JSON Result


//data will be converted to JSON-formatted and returned; also specifies that a GET request is allowed to receive this data
return Json(myObject, JsonRequestBehavior.AllowGet);

Redirect Result

Returns a new url to the browser, which will issue another GET request for that url.

Examples:

//redirects to url for Home Controller > Index Action > with argument name="Smith"
return new RedirectToAction("Index", "Home", new { name = "Smith" });

//redirects to a specific route mapper
return new RedirectToRoute("Default");
Filters

Filters can cancel the generation of an HTTP response, or edit the response.

Filters can be set on individual action methods, on an entire controller, and on a global level.

Filters are good places to put logic that will be repeated across multiple actions/controllers.


[MyCustomFilter]
public ActionResult MyAction() { }

Execution Order

Authentication Filters
Authorization Filters

Pre Action Filters
Action Method Executed
Post Action Filters

Pre Result Filters
Action Result Executed
Post Result Filters

Exception Filters (whenever an exception is thrown)

When using multiple filters of the same type, they could be executed in any order on each request. You can explicitly set an execution order thus:

[MyActionFilter(Order=1)] //1 executes first
For the Action and Result Filters that have a pre and post method, the pre methods will be executed from 1 to N, and the post methods from N to 1.

More generally scoped filters will be executed before more specific ones.

Global Filters

Global-level filters should be registered during Application_Start event.
Add them to Project/App_Start/FilterConfig.cs > RegisterGlobalFilters.

Authentication Filter

Authentication filters validate that the user is who they claim to be.


class MyAuthenticationFilter : IAuthenticationFilter { }

Authorization Filter

Authorization filters validate that the user is allowed to run this action method.


class MyAuthorizationFilter : IAuthorizationFilter { }

Authorize Filter: verifies user credentials before allowing them to access the action.

[Authorize] //user is logged in
public ActionResult MyAction()
{
    return View();
}

[Authorize(Roles="Admin"] //user is logged in as an Admin
public ActionResult MyAction2()
{
    return View();
}

Action Filter


class MyActionFilter : IActionFilter 
{ 
    //before action method
    public void OnActionExecuting(ActionExecutingContext context) { }

    //after action method
    public void OnActionExecuted(ActionExecutedContext context) { }
}

OutputCache Filter: allows the browser to cache the resulting page from this request.

[OutputCache(Duration=60)] //cache result for an hour
public ActionResult MyAction()
{
    //a lengthy operation
    return View();
}

Result Filter


class MyResultFilter : IResultFilter 
{ 
    //before result execution
    public void OnResultExecuting(ResultExecutingContext context) { }

    //after result execution
    public void OnResultExecuted(ResultExecutingContext context) { }
}

Exception Filter

Exception filters are good for logging, or for showing custom error pages.


class MyExceptionFilter : IExceptionFilter  { }

Custom Filters

- Add new class to Project/Filters/MyNameAttribute.cs (Ex: LogAttribute.cs)

using System;
using System.Web;
using System.Web.Mvc;

namespace MyApp.Filters
{
    public class LogAttribute : ActionFilterAttribute
    {
        //before action executes
        public override OnActionExecuting(ActionExecutingContext context)
        {
        }
        //after action executes
        public override OnActionExecuted(ActionExecutedContext context)
        {
        }
        //before result executes
        public override OnResultExecuting(ResultExecutingContext context)
        {
        }
        //after result executes
        public override OnResultExecuted(ResultExecutedContext context)
        {
        }
    }
}
- Fill in whichever event handlers you want to use.

Use custom filters just like normal filters:

using MyApp.Filters;

[LogAttribute]
public ActionResult MyAction()
{
    return View();
}
Model Binding

Default

The model binder maps HTTP request data into your action method's parameters.

The HTTP request data is given by Value Providers. The default ones look in the query string, form data, route data, and posted files.


class MyModelBinder : IModelBinder { }

The default model binders are usually sufficient, even for complex objects.

Example:

public ActionResult Search(string name, int id)
{
    return View();
}
The HTTP request query data will be searched for anything named "name" and "id", with the right data types, and those values will be passed into the action method as arguments. This can work automatically for even complex, nested objects.

The route mapper can also specify how to map url fragments to parameters.

Binding Form Arrays

You can submit a form that has multiple inputs with the same name:

    <input type="text" name="Id" value="1" />
    <input type="text" name="Id" value="2" />
    <input type="text" name="Id" value="3" />
This will be submitted as an array of integers named "Id".

For complex (multiple input) types, this may not bind to the model reliably.

For instance, an unchecked checkbox will not submit at all.
This submits as: string[] { "on" } instead of string[] { "on", "off", "off }

    <input type="checkbox" name="IsEdited" checked />
    <input type="checkbox" name="IsEdited" />
    <input type="checkbox" name="IsEdited" />

To keep complex data related correctly, add an index to the name:

    <input type="text" name="[0].Id" value="1" />
    <input type="checkbox" name="[0].IsEdited" checked />

    <input type="text" name="[1].Id" value="2" />
    <input type="checkbox" name="[1].IsEdited" />

    <input type="text" name="[2].Id" value="3" />
    <input type="checkbox" name="[2].IsEdited" />
The indexes must start at 0 and step by 1.

To use arbitrary indexes:

    <input type="hidden" name="products.Index" value="cold" />
    <input type="text" name="products[cold].Name" value="Beer" />
    <input type="text" name="products[cold].Price" value="7.32" />
    
    <input type="hidden" name="products.Index" value="123" />
    <input type="text" name="products[123].Name" value="Chips" />
    <input type="text" name="products[123].Price" value="2.23" />
    
    <input type="hidden" name="products.Index" value="caliente" />
    <input type="text" name="products[caliente].Name" value="Salsa" />
    <input type="text" name="products[caliente].Price" value="1.23" />

Custom

Custom model binder:

using System;
using System.Collections.Generic;
using System.Web.Mvc;

//the website form contained multiple inputs with name "CustomerId"
//these came in as an array of integers called "CustomerId"
public class MultipleIdsBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if(bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        return (int[])bindingContext.ValueProvider.GetValue("CustomerId").ConvertTo(typeof(int[]));
    }
}

Specifying a custom binder for an action parameter:

public MyController
{
    public ActionResult MyAction([ModelBinder(typeof(MultipleIdsBinder))]int[] ids)
    {
        //stuff
    }
}

Specifying a custom binder for a class:

[ModelBinder(typeof(CustomModelBinder))]
public class MyClass
{
}

Alias

Give an action parameter an alias that the model binder will use instead of the parameter name.


using System.Web.Mvc;
...
public void MyAction([Bind(Prefix="id")] int productId)

(.Net 4.5)

This parameter attribute specifies what query string parameter to map to an action parameter.


using System.Web.ModelBinding;
...
public void MyAction([QueryString("productID")] int? id)

Exclude/Include

You can set a blacklist of fields with "Exclude" or set a whitelist of fields with "Include". This sets what fields the model binder will take into account.

using System.Web.Mvc;
...
public void MyAction([Bind(Exclude="Name,StartDate")] MyModel model)

You'd only need Exclude if there are fields in your model that you don't want set from HTTP Request data.
An alternative is to make an input-only model that is simply missing those fields.

Security

Overposting aka Mass Assignment:
Since the model binder will match as much data as possible from the HTTP Request to the action parameters, you must assume that attackers will try adding their own parameters to the HTTP Request.

Ex: If there is a field that should not be editable after the record is first saved, you'll need to enforce that rule on the backend. It will not be enough to make the value not-editable on the web page.

Session Variables

TempData

A dictionary maintaining data between controllers and actions, even over redirect requests.

Once you view a piece of data in TempData, it is automatically set to NULL.


TempData.Keep[key] //TempData will keep the value of the key even though you viewed it
TempData.Peek[key] //Lets you view the value of key without deleting it

ViewData

A dictionary maintaining data between controllers and views, ie. the data can be set in the controller and used in the view.

ViewBag

A dynamic wrapper around ViewData. You can add dynamic properties to ViewBag.

Provided Data

There are many global objects available in controllers and views.

RouteData

Holds information from the route handler.


string controllerName = RouteData.Values["Controller"];
string actionName = RouteData.Values["Action"];
string parameterValue = RouteData.Values["id"];

Server

Holds utilities.

Prevent cross-site scripting attacks:

string htmlEncodedText = Server.HtmlEncode(text);
Views

ViewStart

~/Views/_ViewStart.cshtml is run before every full view. It sets the default Layout.

_ViewStart files are run in a hierarchy.
If you add ~/Views/Contact/_ViewStart.cshtml, then all the "Contact" views will run this _ViewStart instead of the global one.

The Layout file can also be specified within a specific view.

@{
    Layout = "~/Views/Shared/OtherLayout.cshtml";
    //or use no Layout at all
    Layout = null;
}

Full Views

Strongly-typed views declare their model type at the top of the file. An error will occur if you pass the wrong object type into these views.


@model MyNamespace.MyObject

Partial Views

A partial view is a view that is rendered as part of another view. A single *.cshtml file can be used as both a full view and a partial view. When used as a partial view, it will not run _ViewStart.cshtml.


//returns a result to the current view
@Html.Partial("MyPartial")

//renders straight to the Response object
@{ Html.RenderPartial("MyPartial"); }

Partial views automatically get a copy of the full view's ViewData dictionary - so changes made in the partial view will not affect the full view.


//passing a model to the partial view
@Html.Partial("AuthorPartial", book.Author)

Global Namespaces

You can set "using" statements that are global to all your views, so that they don't each individually need "using" statements.

1) Open file Project/Views/Web.config
2) Update this section

    <system.web.webPages.razor>
        <pages pageBaseType="System.Web.Mvc.WebViewPage">
            <namespaces>
                <add namespace="System.Web.Mvc" />
                <!-- add more namespaces here -->
            </namespaces>
        </pages>
    </system.web.webPages.razor>

To make sure these changes are picked up, you have to restart Visual Studio.
Layouts

Layouts provide consistent page designs for a website.

The default layout is Project/Views/Shared/_Layout.cshtml. You can add custom layouts in the same place.
This default is set in Project/Views/_ViewStart.cshtml, which runs before every view.

The layout must contain exactly one RenderBody call. This is where the full view will be inserted.


<html>
    <head>
    </head>
    <body>
        <!-- some standard html -->
        @RenderBody()
        <!-- some standard html -->
    </body>
</html>

Individual views can set the layout they will use with:

@{ Layout = "customLayout"; }

Sections

You can add additional pieces of a view to the layout.

In the layout:

<html>
    <head>
    </head>
    <body>
        <!-- some standard html -->
        @RenderSection("sidebar", required:false)    
        <!-- some standard html -->
        @RenderBody()
        <!-- some standard html -->
    </body>
</html>

In the view:

//normal view stuff here - to be inserted at RenderBody

@section sidebar {
    //more view stuff
}

Bundles

A bundle is when ASP.Net takes several server-side files and combines into a single file to send to the client. At the same time, the files can be minified.

This simplifies the application as seen from the client side, and provides better page load time.

Bundles are registered in Project/Global.asax > Application_Start
which calls Project/App_Start/BundleConfig.cs > RegisterBundles

The bundler knows not to include "intellisense" files, and knows how to select "min" files.

Example:

bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
    "~/Scripts/bootstrap.js",
    "~/Scripts/respond.js"));

When running in debug mode, the bundler will not do these operations because minified, bundled files are very difficult to debug.
Debug mode can be turned on/off in Project/Web.config:

    <system.web>
        <compilation debug="true" targetFramework="4.5" />
    </system.web>

Styles

"Styles.Render" provides a virtual path to a css bundle.

Example:

    <head>
        @Styles.Render("~/Content/css")
    </head>

Scripts

"Scripts.Render" provides a virtual path to a javascript bundle.

Generate a script tag for a bundle:

    <head>
        @Scripts.Render("~/bundles/modernizr")
    </head>
Modernizr, in particular, is at the top of the page because it needs to start running before the rest of the HTML is rendered.

Most scripts will be included at the bottom of the page:

            @Scripts.Render("~/bundles/jquery")
            @Scripts.Render("~/bundles/bootstrap")
            @RenderSection("scripts", required: false)
        </body>
    </html>
Putting most scripts at the bottom of the page will give a better load-time to the page, because the HTML can be rendered before all the javascript files are downloaded.
Form Validation

Strongly-typed view controls (like Html.TextBoxFor) can validate form data based on Data Annotations (see C# notes).

In your object:

public class Customer
{
    [Required(ErrorMessage="Customer code is required")]
    public string CustomerCode { get; set; }
}

In your view, to handle front-end data validation:

Html.TextBoxFor(m => m.CustomerCode)
Html.ValidationMessageFor(m => m.CustomerCode)

You can list one ValidationMessage per field, or a single ValidationSummary for the form:

Html.TextBoxFor(m => m.CustomerCode)
Html.TextBoxFor(m => m.Name)
Html.TextBoxFor(m => m.PhoneNumber)
Html.ValidationSummary()

In your code, to handle back-end data validation:

if(newCustomer.IsValid)
{
}

Scripts

The Scripts folder will hold your javascript files.

Default javascript libraries for ASP.Net MVC applications include jQuery, jQuery Validation, and jQuery UI.

Minification

ASP.Net 4 and above includes a feature that will automatically minify javascript files, or find the already minified version if the file is named correctly.

_references.js

File Project/Scripts/_references.js is a list of javascript files commonly used in your project.
This list is used by Visual Studio to provide better Intellisense when editing javascript.

It is correct that these references are inside comments.

This file should not be sent to the client.

Jquery-.intellisense.js

This file helps Visual Studio provide Intellisense for jQuery.

This file should not be sent to the client.

Jquery-.js

This and "min" version of the file are the core jQuery library.
The "unobtrusive-ajax" files are a bridge between ASP.Net and the base jQuery library.

Send this to the client to use the jQuery API on the client-side.

To write your own javascript code that uses the jQuery library, start with this:

$(function() {
    //your code here
});

Jquery-ui-.js

This and the "min" version of the file are a UI extension for jQuery that provides common UI elements such as Dialog or Accordion.

Send this to the client to use the jQuery API on the client-side.

Jquery-validate.js

This and the "min" version of the file are an extension for jQuery that provides the client-side data validation.
The "unobtrusive" files are a bridge between ASP.Net and the base jQuery library.

ASP.Net MVC expects this to be present for form validation.

Knockout

Knockout is a javascript library that provides a View/Model/Controller design on the client-side.

Modernizr

Moderinzr is a javascript library that can detect and enable HTML5 features in a browser.
Script Examples

Examples of unobtrusive javascript:
- separation of display from functionality
- web page can operation fully with javascript disabled

Autocomplete

Example of adding autocomplete to an input field using the jQuery UI library.

Controller action to handle the request for "user has typed this in so far":

public ActionResult Autocomplete(string term) //jQuery UI autocomplete requests send argument "term"
{
    var results = _db.Countries
                    .Where(c => c.Name.StartsWith(term))
                    .Take(10)
                    .Select(c => new { label = c.Name }); //jQuery UI expects results with key "label" and/or "value"
    return JsonResult(results, JsonRequestBehavior.AllowGet); //jQuery UI expects JSON result
}

Razor view:

    <form method="get" action="@Url.Action("Index")">
        <input type="search" name="searchTerm" data-myApp-autocomplete="@Url.Action("Autocomplete")"/>
        <input type="submit" value="Search by Name"/>
    </form>
    @Html.RenderPartial("_Countries", Model)

Javascript:

$(function() {
    const submitAutocompleteForm = function(event, ui) {
        //so that the user does not have to click "Submit" after making a selection from the autocomplete suggestions
        let $input = $(this);
        $input.val(ui.item.label); //because this event might trigger before the input value is actually set
        
        let $form = $input.parents("form:first");
        $form.submit();        
    };
    
    const createAutocomplete = function() {
        let $input = $(this); //wrap the selected HTML input element in jQuery
        let options = {
            //there are many options that jQuery UI Autocomplete supports
            source: $input.attr("data-myApp-autocomplete") //url to request the data from
            ,select: submitAutocompleteForm //this function will be run when a selection is made (optional)
        };
        $input.autocomplete(options); //invoke jQuery UI Autocomplete widget
    };

    $("input[data-myApp-autocomplete]").each(createAutocomplete);
});

Paging Results

1) Use NuGet to include "PagedList.Mvc" in your project.

Controller action must now take paging into account:

using PagedList;
...
public ActionResult Index(int page=1)
{
    int pageSize = 10;
    var results = _db.Countries
                    .Select(c => new MyModel{
                        Name = c.Name
                    })
                    .ToPagedList(page, pageSize); //this extension method was added by PagedList
    return View(results);
}

View must accept new model type "IPagedList":

@model IPagedList<MyModel>
...

View will need the pager widget:

<div class="pagedList" data-myApp-target="#countryList">
    @Html.PagedListPager(Model, page => Url.Action("Index", new { page }), PagedListRenderOptions.MinimalWithItemCountText)
</div>

The new PagedList.css file will need to be included in the web page:

bundles.Add(new StyleBundle("~/Content/css").Include(
          "~/Content/bootstrap.css",
          "~/Content/site.css",;
          "~/Content/PagedList.css"));

By default, the pager reloads the entire page. Use AJAX instead:

$(function() {
    const getPage = function() {
        let $a = $(this); //wrap the selected HTML anchor element in jQuery
        let options = {
            url: $a.attr("href")
            ,type: "get"
            ,data: $("form").serialize() //includes values from form on current page
        };
        $.ajax(options).done(function(data) {
            var target = $a.parents("div.pagedList").attr("data-myApp-target");
            $(target).replaceWith(data);
        });
        return false; //stop event propagation
    };

    $(".main-content").on("click", ".pagedList a", getPage); //on click, the event will only be raised if a ".pagedList a" was clicked
});
The paging anchor tags are going to be replaced each time the page is changed, so don't attach events to those anchors.
Instead, this attaches to the ".main-content" element found in the global "_Layout.cshtml" layout.

Content

The Content folder will hold your css files.

Content/Site.css is the main css file for the application.
Dependency Resolver

Design Patterns

Dependency Injection means that the concrete class your Action needs is created by the Controller constructor, and is passed into the Action as an Interface implementation. The Action depends on the Interface, and that dependency is injected (passed into) the Action.
(Or something like that. Dependency Injection is not easily supported by ASP.Net MVC.)

Generalized example:

public Caller()
{
    Dependency dependency = new Dependency();
    Callee(dependency);
}

public Callee(IDependency dependency)
{
    //do something
}

A Service Locator is a class that holds references to many dependencies that might be needed.
The Service Locator does not need to be passed around, it can be called from whereever it is needed.
It will provide whichever dependency is required.

Generalized example:

public Caller()
{
    ServiceLocator.Register<IDependency, Dependency>();
    Callee();
}

public Callee()
{
    IDependency dependency = ServiceLocator.GetService<IDependency>(); //returns a new Dependency object
    //do something
}

IDependencyResolver

As of ASP.Net MVC 3, the DependencyResolver class is available as a Service Locator.

Just implement this interface:

public interface IDependencyResolver
{
    Object GetService(Type serviceType);
    IEnumerable<Object> GetServices(Type serviceType);
}

Register

To register your custom resolver:

protected void Application_Start()
{
    MyResolver resolver = new MyResolver();
    DependencyResolver.SetResolver(resolver);
}

Usage

To use your resolver:

public ViewResult MyAction()
{
    Dependency dependency = DependencyResolver.GetService(typeof(IDependency));
}
Authentication

Windows Authentication

aka IntegratedAuth
aka Single Sign On (a more general term)

Usually used for intranet (internal to a company) applications.
It uses the Windows operating system for identification and authorization. Uses Active Directory.
Once a user is logged into the Windows domain, they can be automatically logged into the intranet application.

When creating a new ASP.Net MVC project, use the "Intranet Application" template to set most of this up automatically.
This will also display some instructions for setting up IIS to work with your application.

By default, users will not be able to access ANY part of the web page until they have logged into the Windows Domain.
Note that Internet Explorer will still display its own login popup if it thinks the web page is not running on your local network. And "localhost" is not considered to be on the local network.
To change that locally:
1) In Internet Explorer > open Tools menu (Alt-T) > Internet Options
2) Security tab > Local Intranet > Sites > Advanced
3) Enter "http://localhost" and click "Add" to whitelist this address.
RELATED SETTINGS (do not change)
1) In Internet Explorer > open Tools menu (Alt-T) > Internet Options
2) Security tab > Local Intranet > Custom level > scroll down to User Authentication and view these settings

Setup:
1) In Visual Studio, select the application in Solution Explorer > click View menu > Properties Window
2) Expand "Development Server" section > set Windows Authentication to Enabled

Project/Web.config:

    <system.web>
        <authentication mode="Windows" />
    </system.web>

Decorate controller actions with Authorize attributes.

//authorize specific users
[Authorize(Users=@"Domain\UserA,Domain\UserB")]

//authorize specific roles
[Authorize(Roles=@"Domain\ManagersA,Domain\ManagersB")]

//authorize specific roles
[Authorize(Roles=@"VicePresidents")]

Or make an explicit role-check in code

bool hasRole = System.Web.HttpContext.Current.User.IsInRole(@"Domain\Role");

In Visual Studio, create a new MVC project with Windows Authentication already setup:
    New Web App > MVC > change Authentication to "WindowsAuth" > ok

In the view:

//holds Domain\username of the logged in user
@Context.User.Identity.Name

//holds the domain the application is running under
@Environment.UserDomainName

//holds the username the application is running as
@Environment.Username

To list all the roles the current user has:

<!-- in web.config, enable role manager -->
<configuration>
    <system.web>
        <roleManager enabled="true" />
    </system.web>
</configuration>

//in controller
using System.Web.Security;
...
string[] roles = Roles.GetRolesForUser();

Views have access to:

    @User.Identity.IsAuthenticated
    @User.Identity.AuthenticationType
    @User.Identity.Name //format "Domain/Username"
    if(User.IsInRole("roleName")) { }

Forms Authentication

Check logins yourself.
Works for internet applications (the user is not logged into your company's domain).

Uses cookies on the user's computer to track session.

SSL is required to make the authentication secure - this is what encrypts the username/password when the user sends it from their computer to your server.

Project/Web.config:

    <system.web>
        <authentication mode="Forms" />
    </system.web>

If starting from a template, look in the "Accounts" controller/views to see basic registration and login pages already setup.
If starting from a template, check how the database login is setup because you may want to make a bit more efficient.

Create a web page with a login form.
When the user tries to access any page that requires authentication, if they are not logged in they will be redirected to this login page. The user will be automatically redirected back their original page if they login successfully.

Create a controller action to handle login:

//in a controller
public ActionResult Login(string username, string password, string sourceUrl)
{
    if(service.ValidateLogin(username, password))
    {
        FormsAuthentication.SetAuthCookie(username, true);
    }
    return RedirectToView(sourceUrl);
}

//in Web.config
    <system.web>
        <authentication mode="Forms">
            <forms loginUrl="~/MyController/Login" timeout="2880" />
        </authentication>
    </system.web>

Still add the authorize attribute to controller actions that require the login:

[Authorize]
public ActionResult MyAction()
{
}

Or set an entire Controller as requiring authorization, and just one action within as not requiring authorization:

[Authorize]
public class MyController : Controller
{
    [AllowAnonymous] //overrides Controller-level attribute
    public ActionResult Index()
    {
        ...
    }
    ...
}

OpenID, OAuth

Open standards for identification (OpenID) and authorization (OAuth).

Also works for internet applications.

Your users do not set a password on your site, and you don't store or validate their login.
The user logins in with a third-party (e.g. Facebook, Google, etc) and that service tells your site who the user is.
Your application will need to be registered with that third-party.

Uses open-source .Net OpenAuth.

Similar to Forms Authentication, your site will redirect unauthorized users to a login page.


Site Attacks

CSRF: Cross-Site Request Forgery

Consider when using Forms Authentication.

The attacker creates their own web page, for something unrelated.

The attacker's webpage includes a hidden form that submits to the same location as one of your forms, and has all the same fields with that attacker's values already filled in.

The attacker waits for someone who is currently logged into your site to come to their page, so they can silently submit this form using that user's authentication cookie. The cookie is automatically sent with the request, exactly as it should be for any request to your site.

From the user's point of view, they just opened some random web page and nothing unusual happened.

To protect against this, add the "ValidateAntiForgeryToken" to any Action that accepts post requests and requires authorization:

//in controller
[HttpPost]
[Authorize(Roles="admin")]
[ValidateAntiForgeryToken]
public ActionResult MyAction()
{
    ...
}

//in view
@using(Html.BeginForm()) {
    @Html.AntiForgeryToken()
    ...
}
Probably best to check for that automatically when the post/authorization conditions are true. Can probably insert that into the Request Life-Cycle somewhere.

Whats happening here is that you send a "i am valid" value to the user with the form.
That value is saved to a cookie.
That value is returned when the form is submitted, and checked by ValidateAntiForgeryToken.
The attacker cannot spoof this value because you cannot set cookies to another site.

Caching

OutputCache

(ASP.Net Framework, not specific to ASP.Net MVC)

The OutputCache action filter lets you store the return value of an Action in memory. Future HTTP Requests to that Action (with the same parameter values) can be responded to with the cached result very quickly. This is handled by ASP.Net.

This is most effectively used on actions that are called frequently, or on actions that take a long time to respond.


[OutputCache]
public ActionResult MyAction()
{
    ...
}

Options:

//how long until the cached value is cleared
[OutputCache(Duration=minutes)]

//specify which parameters matter
//the default is "*" meaning "every parameter"
//"none" means no parameters
[OutputCache(VaryByParam="myParamA;myParamB")]

//"Server", "Client", "ServerAndClient", etc - see System.Web.UI.OutputCacheLocation enum
[OutputCache(Location=OutputCacheLocation.Server)]

//vary caching by HTTP header values
[OutputCache(VaryByHeader="Accept-Language")] //what language is the user reading - critical if you use localization
[OutputCache(VaryByHeader="X-Requested-With")] //this header will be set for AJAX requests only

//write your own custom logic - requires overriding a method in Global.asax
OutputCache(VaryByCustom="??")]

Option to make performance tuning easier:

    //in controller
    [OutputCache(CacheProfile="Aggressive")]

    //in Project/Web.config
    <system.webServer>
        <caching>
            <outputCacheSettings>
                <outputCacheProfiles>
                    <add name="Aggressive" duration="300" />
                    <add name="Mild" duration="10" />
                </outputCacheProfiles>
            </outputCacheSettings>
        </caching>
    </system.webServer>

A use case to watch out for:
Your site is paging results, and only returns the internal part of the web page when you page. That operates using Asynchronous Requests.
The user saves a bookmark and then opens the bookmark later. That operates using a GET Request.
The user may get just the internal part of the web page, which will look a mess, instead of the entire page they expect.
Options:
A) this specific problem can be solved by taking HTTP headers into account when caching
A2) since browsers may implement their own caching, specify that Location="Server" so only your site decides what is cached
B) alternately, separate your actions by request type
Localization

You can set these settings manually, or let ASP.Net do it automatically based on the HTTP Request header "Accept-Language".

To make this automatic, add globalization to the Web.config:

    <system.web>
        <globalization culture="auto" uiCulture="auto" />
    </system.web>

Current Culture

Impacts formatting.


//setting
Thread.CurrentCulture

//affects things like this
DateTime.Now.ToString()

Current UI Culture

Impacts resource loading.


//setting
Thread.CurrentUICulture

Resource Files

Use resource files (*.resx) to localize string literals and binary assets (such as images).
You'll need one resource file for each language you explicitly support.

Example file structure:
Strings.resx: stores default language
Strings.es.resx: stores Spanish language, named following the .Net convention

When adding a new file to the project, look for file type "Resources File".
The access modifier should be "public" (see file edit window) because Razor foods are compiled into their own assembly.
The build action should be "Embedded Resource" (see file properties).

To use these values:

//in view
@Resources.Strings.Greeting

//in action
string greeting = Resources.Strings.Greeting;

//in property attributes
[Required(ErrorMessageResourceType=typeof(Namespace.Resources), ErrorMessageResourceName="ErrorForPhoneNumber")]
public string PhoneNumber { get; set; }

You can add resource files at any folder-depth in your project. To use these files, simply specify the full namespace.
Configuration

Hierarchy

There is a hierarchy of configuration files on the machine. Lower-level configs will override higher-level configs (however, machine administrators can lock-in certain settings).

(High Level)
Machine config (ex: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machine.config)
Machine web.config (ex: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config)
Parent's web.config (optional)
Application web.config
other web.config (optional, such as ~\Views\Web.config or ~\Views\Home\Web.config)
(Low Level)

Web.config

database connection strings
globalization settings
custom errors
authentication

To store custom settings here:

    <appSettings>
        <add key="myKey" value="myValue" />
    </appSettings>
To retrieve that value:

using System.Configuration;
...
public ActionResult MyAction()
{
    string setting = ConfigurationManager.AppSettings["myKey"];
}
Deploying

Assumes deployment to full version of IIS (Internet Information Services).

IIS Installation

1) Download "Microsoft Web Platform Installer"
2) Use it to install "IIS: ASP.Net 4.5"
3) Use it to install "IIS Management Console"
4) Use it to install "SQL Server Express 2008 R2"
5) Use it to install "SQL Server 2008 R2 Management Studio Express with SP1"

(IIS is not intended to be used with LocalDb, it requires a lot of configuration, so switching to SQL Server Express now.)

SQL Server Express will ask for the "sa" password - the administrator password for the SQL Server.

Verify Installation

1) Go to "localhost" in a browser
2) You'll see the IIS splash screen.

1) Open SQL Server Management Studio (SSMS)
- either specify <serverName>\SQLEXPRESS
- or .\SQLEXPRESS
2) The connection will work, and there won't be any database yet.

(SSMS can also connect to LocalDB, using the same "server name" as in your Web.config file)

1) Open the IIS Management Console
2) "...stay connected with latest Web Platform Components?" - No, if just learning the tool
3) The console opens.

Application Pools

In IIS Management Console, this is a process that one or more applications will run inside of.

We'll use the DefaultAppPool to get started.

Sites

In IIS Management Console, this is where all websites are listed. The default is just "Default Web Site".

Prepare For Deployment

Turn off automatic database migrations:

internal sealed class Configuration : DbMigrationConfiguration<AppDb>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }
}

Delete the initial database migration script - it is out of date. (~/Migrations/*_InitialCreate.cs)
Delete the dev database from LocalDb. (easy to do through SSMS)
In Package Manager Console: "Add-Migration InitialCreate".
(If it makes sense) Tell the application to run the migration when it first starts:

using System.Data.Entity.Migrations;
using MyApp.Migrations;
...
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        var migrator = new DbMigrator(new Configuration());
        migrator.Update();
        ...
        AreaRegistration.RegisterAllAreas();
        ...
    }
}
//only do this if NOT using "Run Code First Migrations" in Publishing Profile

First Deployment

1) In Visual Studio > Right-click on web project > Publish
2) Select a Publishing Profile
2A) New Profile
2B) Name: "Release"
2C) Publish Method: Web Deploy
2D) Service URL: localhost (requires administrator privileges, so run Visual Studio as admin)
OR
2C) Publish Method: Web Deploy Package
2D) Package Location: <somewhere on the file system>.zip
2E) Site: "Default Web Site" (i.e. localhost)
3) Configuration: Release
4) Default Connection: ".\sqlexpress" using Windows Authentication with database name "MyAppProduction"
- the Web.config will be deployed with this default connection string
5) Publish
6) Open Command Prompt as Administrator
7) Navigate to the *.zip directory
8A) Run "release.deploy.cmd /T" to test the deployment, telling you about errors that may occur
8B) Run "release.deploy.cmd /Y" to deploy
9) Go to "localhost" in browser to see the site

IIS worker process: w3wp.exe runs under identity "IIS APPPOOL\DefaultAppPool".
This user account will need permission to access your database.

1) Open SSMS > Security > Logins > New Login
2) Login Name: "IIS APPPOOL\DefaultAppPool"
3) User Mapping > check next to "MyAppProduction"
4) Check off "db_datareader", "db_datawriter", and "db_ddladmin"
5) Ok

Future Deployments

1) In Visual Studio > Right-click on web project > Publish
2) Select Publishing Profile "Release"
3) Publish
4) Open Command Prompt as Administrator
5) Navigate to the *.zip directory
6) Run "release.deploy.cmd /Y" to deploy
7) Refresh web page to see changes

Deploy To Azure

1) Go to WindowsAzure.com (Microsoft's cloud platform, to host websites, database, and virtual machines)
2) Log in to Azure Portal
3) Add new website, with database
3A) URL: MyApp.azurewebsites.net
3B) Region: West US (what region the datacenter is in)
3C) Database: Create new SQL Database
3D) Connection string name: "MyAppDb"
4) Go into website dashboard > Download Publishing Profile > save as MyAzureProfile

5) In Visual Studio > Right-click on web project > Publish
6) Select Publishing Profile "MyAzureProfile" (might need to import it)
7) Publish
8) View web page at MyApp.azurewebsites.net


Files

Global.aspx

This is a default file included in MVC applications.

Global.aspx includes class MVCApplication (derived from HTTPApplication). This class exposes many lifecycle events you can hook into.

Diagnostics

ASP.Net Health Monitoring

In machine-level Web.config file:
(Example location C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config)

    <system.web>
        <healthMonitoring enabled="true">
            <providers /> <!-- set publication destinations for this information -->
            <rules> <!-- maps event buckets to providers -->
                <add name="All Events" eventName="All Events" provider="SqlWebEventProvider" />
            </rules>
            <eventMappings /> <!-- categorizes all possible events into buckets with friendly names -->
        </healthMonitoring>
    </system.web>
This file applies to every ASP.Net application running on this computer, with the specified framework version.

Log4net

Elmah.MVC

ELMAH = Error Logging Modules And Handlers.

Open-source tool available through NuGet.
Supports sending error messages as email, or posting to a web site, etc.

With no code changes or setup, you can go to http://localhost:<port>/elmah to see a report of your errors.

See the new elmah section of Project/Web.config for settings such as what roles can view the elmah page, what url route to use, etc.

P&P Application Logging Block
Error Handling

HTTP Result

When an exception occurs while processing an HTTP Request, the programmer will see the "yellow error page" returned. It has a lot of detailed information about the error. This should not be shown to normal users, both for security reasons and ease-of-use reasons.

ASP.Net helps by only showing the "yellow error page" when the HTTP request originated from the same machine that the application is running on (the developer's computer). Otherwise, a more generic error page will be shown.

For a developer to see the generic error page:
- Open Web.config
- Add the "customErrors" section as shown:

  <system.web>
    <customErrors mode="On"/> <!-- default is "RemoteOnly" -->
  </system.web>
- save changes (changes to this file will cause the application to restart if it is already running)
- refresh the web page to see the error page that users will see

The default error page is at Project/Views/Shared/Error.cshtml.
Redirecting to this view is setup in Project/App_Start/FilterConfig.cs > RegisterGlobalFilters.
Unit Testing

I don't know yet how useful this level of unit testing of the front-end is. Front-ends tend to change rapidly. How many of the tests will have to updated frequently?

Basic

(Assuming you told the template project to include a test project when the solution was first created.)

(See C# Unit Testing notes for most information about using the Microsoft.VisualStudio.TestTools.UnitTesting library. This page focuses on anything specific to ASP.Net applications.)

This shows how to test a controller without using IIS or a real HTTP request. Since most of the work of an MVC application occurs in the controllers, they are the most important element to test.

Basic example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AppA;
using AppA.Controllers;

namespace AppA.Tests.Controllers
{
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void Index()
        {
            // Arrange
            HomeController controller = new HomeController();

            // Act
            ViewResult result = controller.Index() as ViewResult;

            // Assert
            Assert.IsNotNull(result);
        }
    }
}

Ensure a model is passed to the view:

        [TestMethod]
        public void Index()
        {
            // Arrange
            HomeController controller = new HomeController();

            // Act
            ViewResult result = controller.Index() as ViewResult;

            // Assert
            Assert.IsNotNull(result);
            Assert.IsNotNull(result.Model);
        }

Testing Actions

Basic example of testing an Action:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AppA;
using AppA.Controllers;

namespace AppA.Tests.Controllers
{
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void Index()
        {
            // Arrange
            HomeController controller = new HomeController();
            // Act
            ViewResult result = controller.Index() as ViewResult;
            // Assert
            Assert.IsNotNull(result);
        }
    }
}

Dependency Inversion: Database

To isolate your test cases from things like databases and email services. For instance, if HomeController.Index() loads data from a database, how do you test it?

Example of making the database optional by referencing an Interface instead of a Concrete Class.

Setting up the database:

public interface IAppDb : IDisposable
{
    IQueryable<T> Query<T>() where T : class;
}

public class AppDb : DbContext, IAppDb
{
    public AppDb() : base("name=DefaultConnection")
    {
    }
    
    public DbSet<Country> Countries { get; set; }
    
    IQueryable<T> IAppDb.Query<T>()
    {
        return Set<T>();
    }
}

In controller:

public class HomeController : Controller
{
    IAppDb _db;

    //default constructor uses the real database - the web server will use this constructor
    public HomeController()
    {
        _db = new AppDb();
    }

    //constructor that the test cases can use
    //example of dependency injection
    public HomeController(IAppDb db)
    {
        _db = db;
    }
    
    public ActionResult Index()
    {
        var model = _db.Query<Country>.Take(10);
        return View(model);
    }
}

In test project:

[TestMethod]
public void Index()
{
    // Arrange
    FakeAppDb db = new FakeAppDb();
    db.AddSet<Country>((new List<Country>() {...}).AsQueryable());
    HomeController controller = new HomeController(db);
    // Act
    ViewResult result = controller.Index() as ViewResult;
    // Assert
    Assert.IsNotNull(result);
}
...
public class FakeAppDb : IAppDb
{
    private Dictionary<Type, object> Sets = new Dictionary<Type, object>();

    public IQueryable<T> Query<T> where T : class
    {
        return Sets[typeof(T)] as IQueryable<T>;
    }
    
    public void Dispose()
    {
    }
    
    public void AddSet<T>(IQueryable<T> objects)
    {
        Sets.Add(typeof(T), objects);
    }
}

Faking ControllerContext

Example: the Action checks "Request.IsAjaxRequest()" but the test case has not provided an HTTP Request.

In test project:

public class FakeControllerContext : ControllerContext
{
    HttpContextBase _context = new FakeHttpContext();
    
    public override System.Web.HttpContextBase HttpContext
    {
        get { return _context; }
        set { _context = value; }
    }
}

public class FakeHttpContext : HttpContextBase
{
    HttpRequestBase _request = new FakeHttpRequest();
    
    public override HttpRequestBase Request
    {
        get { return _request; }
    }
}

public class FakeHttpRequest : HttpRequestBase
{
    public override string this[string key]
    {
        get { return null; }
    }
}

In test case:

[TestMethod]
public void Index()
{
    // Arrange
    HomeController controller = new HomeController();
    controller.ControllerContext = new FakeControllerContext();
    // Act
    ViewResult result = controller.Index() as ViewResult;
    // Assert
    Assert.IsNotNull(result);
}

Examples


[TestMethod]
public void Create_CountryValid_Saved()
{
    //arrange
    var db = new FakeAppDb();
    var controller = new CountryController(db);
    //act
    controller.Create(new Country());
    //assert
    Assert.AreEqual(1, db.Added.Count); //fakes/mocks can have methods/properties that make testing easy
    Assert.AreEqual(true, db.Saved);    //there's a lot more code in the IAppDb and FakeAppDb to support all this
}

[TestMethod]
public void Create_CountryInvalid_NotSaved()
{
    //arrange
    var db = new FakeAppDb();
    var controller = new CountryController(db);
    controller.ModelState.AddModelError("", "Invalid");
    //act
    controller.Create(new Country());
    //assert
    Assert.AreEqual(0, db.Added.Count);
}