MVC
Models
The model is where the Domain-specific objects are defined. These definitions should include business logic (how objects behave and relate), validation logic (what is a valid value for a given object), data logic (how data objects are persisted), and session logic (tracking user state for the application).
DO separate the model into its own project with a distinct assembly.
For applications with a large complex model, it’s a good idea to create a separate assembly for the model to avoid accidentally mixing concerns. You can then reference the model assembly in your project.
DO put all validation logic in the model.
All input validation should occur in the model layer. This includes client-side validation, which is essential to performance. However, client-side validation can be circumvented (with, for example, tools like Fiddler).
You can use ModelState to add validation checking. The following example shows how to add validation checks to ModelState explicitly:
if (String.IsNullOrEmpty (username)) { ModelState.AddModelError ("username", Resources.SignUp.UserNameError); }
However, given the advances in .NET Framework, the namespace System.ComponentModel.DataAnnotations
should be the preferred method for validation. These annotations are added as attributes to the properties of a model class, as the following example shows:
Public class User { [Required(ErrorMessageResourceName = "nameRequired", ErrorMessageResourceType = typeof(Resources.User))] public String userName { get; set; } ... }
DO define interfaces for data access.
It is preferred that interfaces be used to expose methods on a provider for data access. This reinforces the loosely coupled component design of ASP.NET Core.
Consider using the Entity Framework or LINQ to SQL as the means of creating wrappers around calls to a database.
Views
The primary concern of a view is the presentation of the model. The view is chosen by the controller. Business logic does not belong in the view because business logic is the concern of the model and Controller layer. The view mechanism can be extremely flexible. For example, a view of the model for a web page can be rendered using HTML. Another view of the same model (same data) can be presented in XML and act as a web service.
DO put HTML in Views and Partial Views (not in a controller).
A strength of the view pattern is the readability of view template files. For the default view engine, ASP.NET offers full and partial HTML views (.cshtml), and layout page (.cshtml). A layout page enables you to specify an overall layout for a view. Layout pages can be nested several times to create a hierarchy of available layout types.
The partial view is a powerful extensibility and reuse mechanism. For example, we can include the same view in an admin view without writing another line of code.
DO access data in views using ViewData.
ASP.NET provides the following mechanisms to enable you to access data from view templates:
ViewData.Model object, which is set up in a controller’s action method by passing a model object in the action method’s return statement (return View (myModelObject)).
ViewData dictionary (property bag), which enables you to enter any data into the dictionary in an action method (ViewData[“key”] = value), and then access that same dictionary from within the view.
Whenever possible, you should use the ViewData Model instead of the ViewData dictionary because it provides better type safety. Additionally, you should use either a data access mechanism rather than accessing the Request/Session state directly in the view template.
If you have an object for which you will display multiple properties, you should use ViewData.Model and create a strongly typed view for that object type. For example, if you have a seller’s details page, and the seller class has the name, phone, address, and email properties, you would assign a seller instance to ViewData.Model in the controller before rendering the view. If you have disparate data such as page #, a user’s name, and the current time, use the ViewData dictionary.
Avoid data access in the view when using model binding. In other words, do the data retrieval from the database in the model or a service. Then populate lightweight view model objects from the retrieved data in the controller before executing the view. So the lightweight view model objects do not retrieve data during view execution.
DO insert server-side comments in templates.
Use server-side comments in the view templates for commenting. These comments are stripped out before the server returns the HTML representation.
Do not use HTML comments in the view template because they will be rendered to the web browser and could be viewed by an advanced (and potentially malicious) user.
DO use HTMLHelper extension methods.
The System.Web.Mvc.Html class contains useful HTML extension methods. These extension methods include helpers for:
- Form generation (BeginForm)
- Input field generation (checkbox, hidden, radio button, textbox)
- Link generation (ActionLink)
- XSS protection (Encode)
Controllers
The controller (and a specified action method) is invoked by the routing system, given a pattern match on the URL. The controller receives input from the routing system, including the HTTP request context (session, cookies, browser, etc.).
DO use model binding instead of manually parsing the request.
ASP.NET MVC abstracts much of the rote code of object deserialization by using model binding. Model binding is a mechanism by which request context data is marshaled through reflection into the object type defined on the action method.
The model binding system also runs the validation logic applied to the model object, such as the data annotations attributes.
The model binding system has a rich extensibility mechanism that allows full customization of how objects are created, populated, and validated.
DO explicitly name views in action methods.
After setting up the context in the action method for HTML generation, you will return a ViewResult or a PartialViewResult object. If you do not pass a view name to the result class, the view file will be chosen based upon the receiving action name.
For example, given a controller named Products with an action named List. You can call “return View()” from within the List action method without any parameters. The framework will look for a view called /Views/Products/List.cshtml. If that view is missing, it will try /Views/Shared/List.cshtml. So, you can use /Views/Shared for any views that are shared across multiple controllers.
To avoid confusion, explicitly name the view, such as “return View(“explicitViewName”),” in the action method. This allows you to call List from a different action, without the framework looking for a different view.
DO use Post/Redirect/Get (PRG) when submitting forms.
According to the definition of the HTTP POST and GET verbs:
- HTTP GET is used for non-changing (idempotent) data to your model.
- HTTP POST is used for changing data to your model.
Given this clear delineation, when receiving form data in your post back action method, return RedirectToAction(<actionName>), which will result in a HTTP 302 (temporary redirect) and will generate a GET on the <actionName>. This results in a Post-Redirect-Get pattern.
Therefore, do not use HTTP GET for posting form data, as this is a violation of the HTTP GET verb’s purpose.
Additionally, classic ASP.NET postbacks can be the cause of a problematic feedback loop when posting forms.
Routing
Routing is used in ASP.NET MVC to map URLs directly to a controller, rather than a specific file. This is especially useful for readability, as the developer can focus on designing human-readable URLs (for example, product support and search engine indexing).
The default routes are added to a RouteTable, which is located inside the Startup.Configure
method. The table allows you to map a specific URL to a controller and action.
DO order routes from specific to general when using standard routing.
The route table is ordered, therefore create routes from most specific to general.
DO use named route mechanism to avoid route ambiguity.
When you rely upon the ASP.NET routing mechanisms, you must know how the routing mechanisms work. Otherwise, you can create a lot of extra work tracking down misbehaving routes. One way to mitigate this problem is to explicitly name the routes.
For example, the following route mapping define named routes:
app.UseMvc(routes => { routes.MapRoute( name: "areas", template: "{area:exists}/{controller}/{action}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Project}/{action=Dashboard}/{id?}"); });
Extensibility
Inside of the ASP.NET Core framework, there are many points for extension. You can replace any of the core components, a partial list of which includes the following:
routing engine (MvcRouteHandler)
controller factory (IControllerFactory)
view engine (IViewEngine)
For example, you might want to write your own controller factory that uses an inversion of control container.
Rewriting core components is outside the scope of this topic. However, you could extend the framework by adding custom behaviors in the form of filters. Some of the standard filters included in the framework are: OutputCache, HandleError, and Authorize.
DO use filters for adding behaviors.
MVC has a mechanism to add behaviors (filters) through attributes before and after action methods and auction results. These filters allow for extensibility, which is lightweight in the request handling pipeline.
Filters can be applied to a specific action method to alter its behavior. Alternatively, a filter can be applied to a controller class, in which case it will take effect on every action method in the controller class. Base classes can define common behavior patterns by applying filters to the base controller class and ensuring that other controllers derive from that base class.
For example, suppose that you want to add functionality to log information for each request to debug a problem with HTTP headers. The following code defines a class that derives from the ActionFilterAttribute class.
public class LogHeadersFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { foreach (string header in filterContext.HttpContext.Request.Headers.AllKeys) { Debug.WriteLine("Header " + header); Debug.WriteLine("Value " + filterContext.HttpContext.Request.Headers.Get(header)); } base.OnActionExecuting(filterContext); } }
To add this filter to a given action method, you simply place the LogHeadersFilter attribute at the top of the action (or controller) that you want to filter.
Testability
One of the MVC pattern’s major strengths is the improved testability of the designs by keeping concerns separated and decoupled. In ASP.NET Core, you can cleanly separate and test the business logic in the model. For example, you could test the logic for adding a bid to the auction site without depending upon either the controller or the view.
DO write unit tests.
ASP.NET Core provides many of the tools developers need to create testable applications. It is also relatively easy to add a third-party unit test framework, mocking framework, or dependency injection framework. It is beyond this topic’s scope to tell you how to create unit tests for your application. However, ASP.NET Core provides a flexible architecture that allows for easy testing. Unit testing is easier because of features like pluggable view engines, controller factories, action result types, and wrappers around the ASP.NET context types.
Security
Security is an important aspect of any modern software development project. Although no framework can provide perfect security, you can do much to help safeguard your ASP.NET MVC application.
We strive to comply with the OWASP Top 10 Project recommendations. (OWASP 2019)
The recommendation focuses on the 10 most vulnerable security risks for web applications and makes suggestions to prevent or remediate them:
- Injection
- Broken Authentication and Session Management
- Sensitive Data Exposure
- XML External entity
- Broken Access Control
- Security Misconfiguration
- Cross-Site scripting
- Insecure deserialization
- Using 3rd party tools
- Insufficient logging
Threat Modeling
Threat modeling should be an essential part of any software design. Define your security needs/requirements. Create a diagram to use as a visual representation of your system. Use this to identify the security boundaries and flaws. Make plans to mitigate the flaws. Devise processes or tests that will help validate the model.
Use a Faraday cage whenever possible. 🙂
DO guard against common attack vectors.
Website security needs to concern all web developers writing enterprise-class websites and services. There are a host of well-known attack vectors that you should know about. These attack vectors include (but are not limited to):
- Cross-site scripting (XSS) attacks
- SQL injection
- Cross-site Request Forgery (XSRF)
- Improperly implementing model binding.
- To prevent cross-site scripting (XSS) attacks:
- Disable request validation through the use of the ValidateInput attribute. This attribute will falsely reject valid HTML input.
- Add
Html.Encode
for all user input data that is displayed, whether immediately rendered or the data is put into the database and then displayed later. - Set the HttpOnly flag on cookies. This will prevent JavaScript from reading and sending cookies.
To prevent SQL injection:
- Always use parameterized SQL queries.
- Do not pass raw SQL to the database.
- Use an object-relational mapper (ORM) such as Entity Framework, which can completely eliminate the need to have SQL statements in the application code.
To prevent cross-site request forgery (XSRF):
Use the Html.AntiForgeryToken class in forms to protect against cross-site forgery requests. On the action which takes the post request, place the ValidateAntiForgeryToken attribute
To properly implement model binding:
Explicitly call out which members of a class you’re model binding to, use the [Bind (Include=explicit members here)] attribute above the class declaration. For example, if there is an “IsAdmin
” field on a class, automatic binding without Bind directives would allow it to bind to this class.
Alternatively, create so-called view models, which are model objects specifically designed to accept input from external sources. View models contain only properties that are safe for external input. After model binding has created, populated, and validated these objects, these objects’ property values can be mapped to the application model or data model for further processing.
DO authenticate and authorize users to protect content.
It is beyond the scope of these guidelines to provide a deep treatment of authentication and authorization. However, you must annotate restricted data views by writing your own RoleProvider, or judicious use of the Authorize filter attribute.
Localization and Globalization
Globalization is the process of making a product multi-lingual, where localization is the process of adapting a global product for a particular language and country. To develop a web application that supports globalization and localization, keep at least one rule in mind. Do not use hard-coded strings in views.
DO use ASP.NET special resource folders and resource files.
While writing your web pages, add an ASP.NET project folder for globalized content (App_GlobalResources) and localized content for a given view (App_LocalResources). You should add a resource (.resx) file that you should name according to the controller name in each of these folders. In other words, if your controller is named SubmissionPipeline, the resource file should be named SubmissionPipeline.resx.
Visual Studio converts this text mapping class into a global class that you can call using the following syntax:
Resources.<resource_filename>.<string_name>
Then you would access the resource in the view like this:
@Resources.SubmissionPipeline.continueButton
When the translated resource files become available, you should name each file using the following format: <filename>.<language>.resx
.
For example, the German version of the resource file would be named: SubmissionPipeline.de.resx.
Performance
Performance is a multi-faceted problem for web-sites, as a myriad of bottlenecks can affect performance, including:
- Database
- Inefficient queries
- Incorrectly placed indexes
- Non-normalized design
- Bandwidth problems
- Large request size (affected by individual large images, .css, .js, .html, etc.)
- Content that references many other items, such as multiple scripts, CSS, or image files
- Slow connection speed
- Processing power
- Server: expensive operations
- Client: poorly written javascript
This section will focus solely on server processing and request size.
DO consider partial page updates using AJAX for reducing bandwidth.
One way to mitigate performance issues involving server processing and request size is to use asynchronous Javascript (AJAX) to do partial page updates. ASP.NET Core has built-in AJAX support, which helps to facilitate this pattern. The performance benefit occurs because the pattern reduces the amount of processing that the server must do to render a request and reduce the HTML fragment size.
DON’T overuse session; instead, use TempData for short-lived (intra-request) storage.
It is tempting when creating websites to add objects to the Session object to be readily available. The problem with doing this is that it can bog down the server by storing extra information that may only be necessary across a redirect. The correct way to store these temporary variables across a redirect is by using the TempData dictionary.
For example, suppose you receive form data from a POST at login. The action method that procedure the POST might be something like the following:
[HttpPost] public ActionResult LogIn(Seller person) { ... TempData["name"] = person.Name; return RedirectToAction("ItemUpload"); }
In this example, before redirecting to the ItemUpload action, the name of the seller is placed in the TempData dictionary. In the ItemUpload action method, the seller name is retrieved from the TempData dictionary and placed in the ViewData dictionary so it can be referenced in the view.
public ActionResult ItemUpload() { string name = TempData["name"] as string; ViewData["name"] = name; return View(); }
DO use a ResponseCache filter for static pages.
Use the ResponseCache attribute when you return less frequently updated data; a good candidate might be your home page. You can use this technique for both HTML and JSON data types. When using it, only specify the cache profile name; Do not specify anything else. If you need to fine-tune caching, use the output cache section of the appsettings.json file.
For example, the ResponseCache attribute is attached to the Dashboard action method in the following code.
[HttpGet, ResponseCache(CacheProfile = "Dashboard")] public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page) { ... }
In the appsettings.json file, the duration is fine-tuned to 15 seconds.
DO consider using asynchronous controllers for long-running requests.
ASP.NET’s threading pool has a default limit of 12 concurrent worker threads per CPU. When the requests overload the server’s ability to process these requests, a queue is built up of requests. For example, any request takes a considerable amount of time waiting for external resources, such as database or large file operations. These external requests block the thread they occupy for the entire wait period. When this queue gets too large (5000 requests pending), the server starts responding with 503 (server too busy) errors.
API
API or Application Programming Interface refers to any means of sharing features/data between programs. However, this term has generally been used to identify interfaces that use the Application layer part of the Internet Protocol Suite in recent years. This section addresses that segment of the API definition and how to best use it.
REST
Representational State Transfer refers to the architectural style of using the HTTP Verbs to represent state or state changes in the application. Therefore this style is stateless because it does not store “state” or “session” information between requests. Each request is a “state” representation. REST is a simple, flexible, lightweight means of exchanging information.
SOA
Service-oriented Architecture is an architectural style for distributed application components as Web Services that incorporate and share access control, data models, and security features. This is accomplished through the implementation of the SOAP specification.
SOAP or Simple Object Access Protocol is a message protocol that enables communication between distributed elements of an application or system. The messages are created in envelopes that are authored in XML and transferred via any of the Application Layer protocols of the Internet Protocol Suite. SOAP is generally considered a strict means of communicating heavy payloads across multiple systems.
REST VS SOAP
This comparison is really an unfair one because we compare an architectural style vs. a message transfer protocol. So let’s set aside the fundamental difference and consider the style of information transfer instead:
- REST sends light JSON or XML messages using the HTTP protocol.
- SOAP sends heavy XML messages using any of the Internet Protocol Suite’s application layer protocol.
- REST is stateless.
- SOAP is stateful or stateless, depending on the implementation.
- REST has no security built into the style but using frameworks helps add the layers.
- SOAP is built with security in mind and is independent of the framework.
- REST is best used for lightweight payloads.
- SOAP shines in heavy payload scenarios.
Frameworks
WCF
The Windows Communication Framework is “Microsoft’s unified programming model for building service-oriented applications.” (Microsoft 2017)
The WCF application template enables the creation of SOA endpoints for loosely-coupled web services. These endpoints can be services hosted in an application, IIS, or cloud environments.
WCF is a framework and not a pattern; therefore, it takes advantage of the multiple transport protocols and message patterns. It provides surprisingly flexible design opportunities while providing built in security. This flexibility is also what makes it significantly more complex to implement.
ASP.Net WebApi
ASP.NET WebApi is Microsoft’s implementation of a pattern that simplifies web services using the MVC framework strictly using the HTTP protocol.
The WebApi application template enables the creation of HTTP service endpoints that can be consumed by any client without knowing anything about the application or system. This pattern simplifies complex communication handshakes and processing to enable the developer to focus on proprietary code. This pattern is immensely easy to implement within a rigid design structure.
It is worth noting that although Web Api does implement the REST pattern, it does not mean that the HTTP services are RESTFull. That is a design pattern that you honor at your own convenience.
Best Practice
Consider using the right tool for the right purpose: WCF is a great tool for complex dependant systems while Web Api shines in non-dependence system schemas. Don’t make an HTTP service act as a URL for a method nor make a WCF service RESTRFull even though it can. Just because it can does not mean that it should.
- Don’t use a screwdriver to pound in a nail. 🙂
- Use REST for lightweight, simple applications that do one thing.
- When implementing RESTFull services, keep them RESTFull. If you find yourself adding “methods” to your model routes, you’ve transitioned out of the RESTFull pattern. Don’t intermix the patterns because it becomes confusing to maintain.
- When using REST/WebApi, if you find your payload exceeding the HTTP protocol standards, consider reducing the payload or changing protocol.
- Use SOAP for Heavyweight payloads.
- If you find that your design requires service methods not related to a model, consider using SOAP.
- When security is central and strict, use SOAP.
WebApi
ASP.Net Core WebApi is built using the MVC pattern and framework. Consider the Section “Best Practices for MVC” as the guide with the modifications that follow in the section.
Do let the framework do the heavy lifting.
MVC and Web API are very flexible. When creating routes, use the simplest form first. If you find that this is not sufficient for you, consider simplifying it rather than going outside of the default behavior.
Consider a model “Batch.” The GET method should return a Batch. If you find that you need to force a JSON return, you are outside of the default behavior.
Consider the following code:
[HttpGet] public IActionResult GetBatches() { var userInfo = GetUserInfo(Request); // Verify authentication if (!userInfo.IsAuthenticated) return Unauthorized(); // Verify authorization IList<Order> orders = userInfo.IsAdmin ? _orderManager.GetAllORdersWithItems() : _orderManager.GetOrdersByUser(userInfo.UserId, true); foreach (Order order in orders) { . . . } return Json(orders, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); }
Can be more flexible like:
[HttpGet] public List<Batch> GetOrders() { var userInfo = GetUserInfo(Request); // Verify authentication if (!userInfo.IsAuthenticated) return Unauthorized(); // Verify authorization IList<Order> orders = userInfo.IsAdmin ? _orderManager.GetAllOrdersWithSamples() : _ orderManager.GetOrdersByUser(userInfo.UserId, true); foreach (Order order in orders) { . . . } return orders; }
This allows the caller to determine if List<Order> is returned in JSON or XML. If you call the method from a Chrome browser, the alternate method will return XML rather than JSON.
Securing an Application
These guidelines are intended to help determine how to secure your application rather than when to secure it. Your threat modeling and security requirement should dictate when to secure your application.
Over the years, there have been many standards, rules, and technologies developed and used for securing applications. We will look at the accepted standards most common in 2019: OAuth 2, OpenID, Identity Framework.
When securing an application, there are two main parts: 1) Authentication, 2) Authorization.
Authentication is simply the process or action of verifying the identity of a user or process.
Authorization is the permission given to a resource, process, or action.
Simply put, in the context of an application, a user is authenticated using a login page or process then authorized by a higher authority like an administrator.
OAuth 2
OAuth 2 is the industry-standard protocol for authorization. It provides simple and robust flows for applications ranging from massive websites to IoT devices. This standard is most widely used to allow an owner (higher authority) to give access (permission) to certain resources. (OAuth Foundation n.d.)
The OAuth 2 standard can be used in many scenarios. In the context of our application development, OAuth 2 best helps us secure API.
OpenID
OpenID Connect is the industry-standard protocol for authentication. It is based on the OAuth 2 specifications. It lets app and site developers authenticate users without taking on the responsibility of storing and managing passwords. (OpenID Foundation n.d.)
In the context of our application development efforts, OpenID is used to provide a means that validate the username and password for a user.
ASP.NET Identity Framework
This is Microsoft’s framework to create custom Authentication and Authorization systems. It is a replacement for ASP.NET Membership and Simple Membership frameworks. It has the benefit of providing a well-defined process for all user management tasks. When combined with Single Sign-On providers, it can provide a robust mechanism for securing and managing user information and authorization.
Single Sign-On Providers
These are systems or sites that manage the server implementation of Identity Verification and/or Permission Validation. Agape.Identity is a service that implements OAuth2 for Resource Authorization, OpenID for Identity Authentication and Identity Framework for User Management.
Other SSO providers include Azure AD, Azure AD B2C, Office365, Okta, Facebook, LinkedIn, Twitter, Amazon, Google.
References
Microsoft. 2017. What Is Windows Communication Foundation? 29 03. Accessed 04 16, 2019.
OAuth Foundation. n.d. OAuth 2.0. Accessed May 2, 2019. https://oauth.net/2/.
OpenID Foundation. n.d. OpenID Connect FAQ and Q&As. Accessed May 2, 2019. https://openid.net/connect/faq/.
OWASP. 2019. OWASP Top Ten Project. 28 Feb. Accessed May 2, 2019. https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project.
Smith, James. n.d. Coding Standards. Accessed Oct 22, 2020. https://expertdev.blog/coding-standards/.