Skip to content

04 - Controllers, Routing

MVC

Just a reminder

MVC

  • Model
    • Business logic and its persistence
  • View
    • Template for displaying data
  • Controller
    • Communication between Model, View and end-user
  • ViewModel
    • Models used for supplying views with complex (strongly typed) data
    • MVC Viewmodels vs MVVM (Model-View-ViewModel - WPF, Mobile, two-way data binding)

MVVM

Controller

  • Controller – define and group actions (or action method) for servicing incoming web requests.
  • Controller can be any class that ends in “Controller” or inherits from class that ends with “Controller”
  • Convention is (but are not required)
    • Controllers are located in "Controllers" folder
    • Controllers inherit from Microsoft.AspNetCore.Mvc.Controller
  • The Controller is a UI level abstraction. Its responsibility is to ensure incoming request data is valid and to choose which view or result should be returned.
    In well-factored apps it will not directly include data access or business logic, but instead will delegate to services handling these responsibilities.

Inheriting from base Controller (MVC) or ControllerBase (Api) gives lots of helpful methods and properties

Most importantly returning various responses

  • View
    • Return View(viewModel);
  • HTTP Status Code
    • Return BadRequest();
  • Formatted Response
    • Return Json(someObject);
  • Content negotiated response
    • Return Ok();
  • Redirect
    • Return RedirectToAction(“Complete”, viewModel);
1
2
3
4
5
6
public abstract class Controller : ControllerBase
{
    public dynamic ViewBag { get; }
    public virtual ViewResult View(object model) { }
    // more View support stuff
}

Routing

Routing middleware is used to match incoming requests to suitable controller and action
Actions are routed by convention or by attribute
Routing middleware (sets up the convention)

1
2
3
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

By "classic" convention, request should take this form
.../SomeController/SomeAction/ID_VALUE

Multiple routes

1
2
3
4
5
6
7
8
app.MapControllerRoute(
    name: "article",
    pattern: "article/{name}/{id}",
    defaults: new {controller = "Blog", action = "Article"}
    );
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
  • Routes are matched from top-down
  • On first match corresponding controller.action is called

Multiple matching action

When multiple actions match, MVC must select suitable action
Use HTTP verbs to select correct action

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class ProductsController : Controller
{
    public IActionResult Edit(int id) { 

    }

    [HttpPost]
    public IActionResult Edit(int id, Product product) { 

    }
}

If not possible to choose, exception is thrown

show form -> submit form -> redirect pattern is common

Routing attributes

With attribute routing, controller and action name have no role!
Typically attribute based routing is used in api programming

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AttributeRouteController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    public IActionResult MyIndex()
    {
        return View("Index");
    }

    [Route("Home/About")]
    public IActionResult MyAbout()
    {
        return View("About");
    }

    [Route("Home/Contact")]
    public IActionResult MyContact()
    {
        return View("Contact");
    }
}

Route Template

  • { } – define route parameters
    Can specify multiple, must be separated by literal value
    Must have a name, my have additional attributes

  • * - catch all parameter
    blog/{*foo} would match any URI that started with /blog and had any value following it (which would be assigned to the foo route value). 

  • Route parameters may have default values
  • name? – may be optional
  • name:int – use : to specify an route constraint blog/{article:minlength(10)}

Route constraints

Avoid using constraints for input validation, because doing so means that invalid input will result in a 404 (Not Found) instead of a 400 with an appropriate error message.
Route constraints should be used to disambiguate between similar routes, not to validate the inputs for a particular route.

Constraints are
int, bool, datetime, decimal, double, float, guid, long, minlength(value), maxlength(value), length(value), length(min,max), min(value), max(value), range(min, max), alpha, regex(expression), required

Model Binding

  • Maps data from from HTTP request to action parameters
  • Order of binding
    • Form values (POST)
    • Route values
    • Query string (…foo/?bar=fido)

Route: {controller=Home}/{action=Index}/{id?}
request: .../movies/edit/2
public IActionResult Edit(int? id)

Model binding also works on complex types – reflection, recursion – type must have default constructor

Model binding attributes

  • BindRequired – adds an model state error, if property cannot be binded (int Foo for example)
  • BindNever – switch off binder for this parameter

If you want to modify the default binding source order: - FromHeader, FromQuery, FromRoute, FromForm – select source - FromBody – from request body, use formatter based on content type (json, xml)

1
2
3
4
5
6
7
8
public class BookOrder
{
    [Required]
    public string Title { get; set; }

    [BindRequired]
    public int Quantity { get; set; } // 0 is assigned by default, bindrequired checks actual form data
}

View scaffolding

Use [ScaffoldColumn(false)] attribute in your model to exclude some property from scaffolding.

Not a proper way to approach this, use correct ViewModels instead (DTO).

Model validation

  • Validation attributes mostly in System.ComponentModel.DataAnnotations
  • [Required], [MaxLength()], etc.
  • Model validation occurs prior to action invocation
  • Action has to inspect ModelState.IsValid
  • If needed, call TryValidateModel(someModel) again

Custom validation, client-side validation, remote validation - it's coming...

ModelState

  • ModelState builds up metadata representation of your viewmodel

  • When repopulating view with data in postback (Edit/Post) ModelState values are used first by tag-helpers/html helper, if not found then values from actual viewmodel. (ie when re-rendering edit view due to validation errors)

  • So, if you need update viewmodel data in Post method, remove responding fields from ModelState
1
2
ModelState.Remove("FirstName");
person.FirstName = "Foo Bar";