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 are that ASP pages are interpreted, like PHP, while ASP.Net 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, 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 (I'm currently using 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>

MVC Application Life Cycle

From when the app starts running on IIS, to when it stops.

Modules

The MVC lifecycle 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 lifecycle events, thus becoming part of the lifecycle.


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.

MVC 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.
Each event here is actually made up of a Pre- and Post- version.

BeginRequest

AuthenticateRequest

AuthorizeRequest

ResolveRequestCache

    PostResolveRequestCache is the event the URL Routing Module listens for

MapRequestHandler

AcquireRequestState

RequestHandlerExecute

    This is where all your MVC code is executed

UpdateRequestCache

LogRequest

EndRequest

MVC Life Cycle Events

Pre Application Start Event

You can set a pre-start method in an attribute at the assembly level.

This is mostly used to register Modules.

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.

Application End Event

Occurs when the application is stopped (may not fire if the application crashes).
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

Register

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.
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.

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.
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);
}