Tag Archives: MVC

Keeping domain models and view models separate with ASP.NET MVC and WCF

The location, role and responsibility of objects within a software system is a common topic in the forums with plenty of disagreement about what is and isn’t correct. But first, let’s just define some commonly found objects and their roles:

Domain repository entity: Object which is an in-memory representation of a record persisted in some sort of backing data store. In most business scenarios, a repository entity will be mapped to a physical database table via an ORM tool such as NHibernate or the Microsoft Entity Framework. 

Domain model entity: An object which lives in the service layer and typically contains both logic and/or data. This is where your business rules should be defined. Often, a domain model entity and a domain repository entity will be the same thing. In other words, an object which contains business logic and is also mapped to a data store (e.g. an Order domain entity may be mapped to an Order database table, but also contains business logic to calculate the total cost of the order, including tax). This arrangement allows data to be processed by business code and persisted via the same object.

Data contract: This is a DTO (data transfer object) which is decorated with the WCFDataContract attribute. This is essentially a ‘dumb’ object containing just data and no logic, and is used to pass data across service boundaries via serialisation. Data contracts will usually by mapped to domain models to copy data from one to the other.

View model: Another type of DTO but limited to just the MVC UI layer. View model objects are used to pass data between MVC controller action methods and the views which display and capture model data.

Passing data between services and the web UI
So with these definitions in mind, it is OK (and necessary) for data contracts which are defined in your WCF service layer to be passed to and from your MVC web application. However for this to be possible, the MVC web app must have access to the data contract type declarations. The way you achieve this is to always have your WCF service implementation and WCF service & data contracts in separate assemblies. Then from your MVC web app, you can reference the project/assembly containing the service & data contracts (but do not reference the service implementation assembly).  Now your MVC web app can happily use data contracts defined in the service layer, and will still compile.

However it’s not OK for domain entities to be passed across the same service boundaries because then you blur the line between what should be a simple data transfer object and something containing logic which should never be directly exposed to your MVC views and controllers. Ignore this at your peril otherwise you will end up with business logic contaminating your UI layer which will lead to no end of problems in the future and result in a lot of refactoring and maintenance.

But how do you keep them apart?  Well from a coding point of view, one approach is that only classes that have absolutely no business logic should be decorated with DataContractand DataMember attributes. This prevents serialisation of domain entities meaning that they can’t accidentally or intentionally be used as a parameter in service contract. Another more basic check is to make sure that the web UI project never references an assembly containing domain entities (sounds obvious but I’ve seen it happen). This will keep them safely isolated from the UI. However from a physical point of view, the simple answer is that you can’t absolutely guarantee this won’t happen. It only takes one developer to unwittingly do the wrong thing and the rot starts to set in.

So if you can’t physically keep them apart, what can you do to contain the problem?  Well you have to rely on some fundamental development techniques which have been around for years: discipline, team communication and code reviews. OK I’m not going to win any awards for innovation here, but building a reliable system is more than just writing a lot of tests and then assuming everything’s OK. You have to enforce design rules and best practices which the development team sticks to, and the appropriate use of domain entities, view models, data contracts, etc. is all part of that.

Passing data within the UI
Any application with an MVC web client and WCF services will reach a point where types defined in the service layer and types defined in the web UI meet, and in most cases that will happen in your MVC controllers. But for most scenarios, that’s the only time they will share the same space. It will also help if you give data contracts and view models different names. View model names typically should reflect the view they relate to, and I usually add a ‘Model’ suffix to them for clarity (but it’s down to personal preference how you do it). However if you are using the SvcUtil tool to generate service proxies, I recommend you specify a namespace so that it’s clear which models are defined in the service layer (see this post for generating service proxies).

In a typical case where you need to get data from a view model object into a data contract object so that it can be sent to a service, all you have to do is map the properties between the two via an object initialiser, in a constructor, or using a mapping tool such as AutoMapper, although AutoMapper can be quite hungy on memory resources so be aware of this when you decide what to use. Writing your own mapping code is trivial so why use anything else?

There’s a bone of contention about whether data contracts defined in the service layer should be used as view models. Personally I don’t have a problem with it and actually prefer to have my view model types declared as data contracts in the service layer rather than in the MVC app. This is so I only have to define data annotation validation rules once rather than defining them against data contracts in the service layer, and then again for  view models in the MVC application. This makes unit/integration testing easier and reduces the chance of a property not being validated properly. As a result, data contracts declared in the service layer are passed directly into my views (so there are very few actual view models declared in the MVC app).

However you may not like this and prefer to have separate, dedicated view models because it avoids the situation where views end up being strongly-typed to classes defined in a different layer of your architecture. How you do it is entirely down to person choice.

original article by Phil Munro

Validating with a Service Layer

original article By Stephen Walther|March 2, 2009

http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs

Learn how to move your validation logic out of your controller actions and into a separate service layer. In this tutorial, Stephen Walther explains how you can maintain a sharp separation of concerns by isolating your service layer from your controller layer.

The goal of this tutorial is to describe one method of performing validation in an ASP.NET MVC application. In this tutorial, you learn how to move your validation logic out of your controllers and into a separate service layer.

Separating Concerns

When you build an ASP.NET MVC application, you should not place your database logic inside your controller actions. Mixing your database and controller logic makes your application more difficult to maintain over time. The recommendation is that you place all of your database logic in a separate repository layer.

For example, Listing 1 contains a simple repository named the ProductRepository. The product repository contains all of the data access code for the application. The listing also includes the IProductRepository interface that the product repository implements.

Listing 1 — Models\ProductRepository.cs

using System.Collections.Generic;
using System.Linq;

namespace MvcApplication1.Models
{
    public class ProductRepository : MvcApplication1.Models.IProductRepository
    {
        private ProductDBEntities _entities = new ProductDBEntities();


        public IEnumerable<Product> ListProducts()
        {
            return _entities.ProductSet.ToList();
        }


        public bool CreateProduct(Product productToCreate)
        {
            try
            {
                _entities.AddToProductSet(productToCreate);
                _entities.SaveChanges();
                return true;
            }
            catch
            {
                return false;
            }
        }


    }

    public interface IProductRepository
    {
        bool CreateProduct(Product productToCreate);
        IEnumerable<Product> ListProducts();
    }


}

The controller in Listing 2 uses the repository layer in both its Index() and Create() actions. Notice that this controller does not contain any database logic. Creating a repository layer enables you to maintain a clean separation of concerns. Controllers are responsible for application flow control logic and the repository is responsible for data access logic.

Listing 2 – Controllers\ProductController.cs

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        private IProductRepository _repository;

        public ProductController():
            this(new ProductRepository()) {}


        public ProductController(IProductRepository repository)
        {
            _repository = repository;
        }


        public ActionResult Index()
        {
            return View(_repository.ListProducts());
        }


        //
        // GET: /Product/Create

        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Product/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude="Id")] Product productToCreate)
        {
            _repository.CreateProduct(productToCreate);
            return RedirectToAction("Index");
        }


    }
}

Creating a Service Layer

So, application flow control logic belongs in a controller and data access logic belongs in a repository. In that case, where do you put your validation logic? One option is to place your validation logic in a service layer.

A service layer is an additional layer in an ASP.NET MVC application that mediates communication between a controller and repository layer. The service layer contains business logic. In particular, it contains validation logic.

For example, the product service layer in Listing 3 has a CreateProduct() method. The CreateProduct() method calls the ValidateProduct() method to validate a new product before passing the product to the product repository.

Listing 3 – Models\ProductService.cs

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

namespace MvcApplication1.Models
{
    public class ProductService : MvcApplication1.Models.IProductService
    {

        private ModelStateDictionary _modelState;
        private IProductRepository _repository;

        public ProductService(ModelStateDictionary modelState, IProductRepository repository)
        {
            _modelState = modelState;
            _repository = repository;
        }

        protected bool ValidateProduct(Product productToValidate)
        {
            if (productToValidate.Name.Trim().Length == 0)
                _modelState.AddModelError("Name", "Name is required.");
            if (productToValidate.Description.Trim().Length == 0)
                _modelState.AddModelError("Description", "Description is required.");
            if (productToValidate.UnitsInStock < 0)
                _modelState.AddModelError("UnitsInStock", "Units in stock cannot be less than zero.");
            return _modelState.IsValid;
        }

        public IEnumerable<Product> ListProducts()
        {
            return _repository.ListProducts();
        }

        public bool CreateProduct(Product productToCreate)
        {
            // Validation logic
            if (!ValidateProduct(productToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateProduct(productToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }


    }

    public interface IProductService
    {
        bool CreateProduct(Product productToCreate);
        IEnumerable<Product> ListProducts();
    }
}

The Product controller has been updated in Listing 4 to use the service layer instead of the repository layer. The controller layer talks to the service layer. The service layer talks to the repository layer. Each layer has a separate responsibility.

Listing 4 – Controllers\ProductController.cs

Listing 4 – Controllers\ProductController.cs
using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        private IProductService _service;

        public ProductController()
        {
            _service = new ProductService(this.ModelState, new ProductRepository());
        }

        public ProductController(IProductService service)
        {
            _service = service;
        }


        public ActionResult Index()
        {
            return View(_service.ListProducts());
        }


        //
        // GET: /Product/Create

        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Product/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
        {
            if (!_service.CreateProduct(productToCreate))
                return View();
            return RedirectToAction("Index");
        }


    }
}

Notice that the product service is created in the product controller constructor. When the product service is created, the model state dictionary is passed to the service. The product service uses model state to pass validation error messages back to the controller.

Decoupling the Service Layer

We have failed to isolate the controller and service layers in one respect. The controller and service layers communicate through model state. In other words, the service layer has a dependency on a particular feature of the ASP.NET MVC framework.

We want to isolate the service layer from our controller layer as much as possible. In theory, we should be able to use the service layer with any type of application and not only an ASP.NET MVC application. For example, in the future, we might want to build a WPF front-end for our application. We should find a way to remove the dependency on ASP.NET MVC model state from our service layer.

In Listing 5, the service layer has been updated so that it no longer uses model state. Instead, it uses any class that implements the IValidationDictionary interface.

Listing 5 – Models\ProductService.cs (decoupled)

using System.Collections.Generic;

namespace MvcApplication1.Models
{
    public class ProductService : IProductService
    {

        private IValidationDictionary _validatonDictionary;
        private IProductRepository _repository;

        public ProductService(IValidationDictionary validationDictionary, IProductRepository repository)
        {
            _validatonDictionary = validationDictionary;
            _repository = repository;
        }

        protected bool ValidateProduct(Product productToValidate)
        {
            if (productToValidate.Name.Trim().Length == 0)
                _validatonDictionary.AddError("Name", "Name is required.");
            if (productToValidate.Description.Trim().Length == 0)
                _validatonDictionary.AddError("Description", "Description is required.");
            if (productToValidate.UnitsInStock < 0)
                _validatonDictionary.AddError("UnitsInStock", "Units in stock cannot be less than zero.");
            return _validatonDictionary.IsValid;
        }

        public IEnumerable<Product> ListProducts()
        {
            return _repository.ListProducts();
        }

        public bool CreateProduct(Product productToCreate)
        {
            // Validation logic
            if (!ValidateProduct(productToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateProduct(productToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }


    }

    public interface IProductService
    {
        bool CreateProduct(Product productToCreate);
        System.Collections.Generic.IEnumerable<Product> ListProducts();
    }
}

The IValidationDictionary interface is defined in Listing 6. This simple interface has a single method and a single property.

Listing 6 – Models\IValidationDictionary.cs

namespace MvcApplication1.Models
{
    public interface IValidationDictionary
    {
        void AddError(string key, string errorMessage);
        bool IsValid { get; }
    }
}

The class in Listing 7, named the ModelStateWrapper class, implements the IValidationDictionary interface. You can instantiate the ModelStateWrapper class by passing a model state dictionary to the constructor.

Listing 7 – Models\ModelStateWrapper.cs

using System.Web.Mvc;

namespace MvcApplication1.Models
{
    public class ModelStateWrapper : IValidationDictionary
    {

        private ModelStateDictionary _modelState;

        public ModelStateWrapper(ModelStateDictionary modelState)
        {
            _modelState = modelState;
        }

        #region IValidationDictionary Members

        public void AddError(string key, string errorMessage)
        {
            _modelState.AddModelError(key, errorMessage);
        }

        public bool IsValid
        {
            get { return _modelState.IsValid; }
        }

        #endregion
    }
}

Finally, the updated controller in Listing 8 uses the ModelStateWrapper when creating the service layer in its constructor.

Listing 8 – Controllers\ProductController.cs

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        private IProductService _service;

        public ProductController()
        {
            _service = new ProductService(new ModelStateWrapper(this.ModelState), new ProductRepository());
        }

        public ProductController(IProductService service)
        {
            _service = service;
        }


        public ActionResult Index()
        {
            return View(_service.ListProducts());
        }


        //
        // GET: /Product/Create

        public ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Product/Create

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
        {
            if (!_service.CreateProduct(productToCreate))
                return View();
            return RedirectToAction("Index");
        }


    }
}

Using the IValidationDictionary interface and the ModelStateWrapper class enables us to completely isolate our service layer from our controller layer. The service layer is no longer dependent on model state. You can pass any class that implements the IValidationDictionary interface to the service layer. For example, a WPF application might implement the IValidationDictionary interface with a simple collection class.

ASP.NET MVC 5 Internationalization · Date and Time

It would be nice if a web site can show times local to the region where the visitor is located instead of server location (web server, database, etc). For example, a user from Spain that is looking at their order summary on some web site expects that their order date and time 28/01/2014 14:32:26 to be a local (Spain) time, and not a web server time or database server time. Although it is not wrong to add a suffix denoting the region such as 28/01/2014 14:32:26 PST, it is much more readable for users to see times local to their region.

The .NET Framework has different types and methods that help deal with different time zones which can be quite complicated. The good news is that in general, time zones are not needed in order to display times based on the user’s location. It is simpler than you might think.

There are different ways of dealing with this problem. An easy and effective way is to store all times in the database as UTC (Coordinated Universal Time) and then adjust time (before display) based on where the user is located in the world. The most common .NET type is DateTime. ADateTime stores a date and optionally a time. It has a Kind property which determines if the value is one of three: local, UTC, or unspecified.

Another type is called DateTimeOffset. A DateTimeOffset stores a date and time relative to UTC. Additionally, it stores an offset from UTC as TimeSpan.

 

1
2
Console.WriteLine (DateTime.Now);         // 1/28/2014 2:53:50 PM
Console.WriteLine (DateTimeOffset.Now);   // 1/28/2014 2:53:50 PM -08:00

 

DateTime and DateTimeOffset differ in how they deal with time zones and in comparisons. For i18n, I prefer DateTimeOffset because it refers to a more precise point in time. Also, SQL Server 2008 and above support DateTimeOffset as a native data type.

How to capture the user’s time zone offset?

With the aid of the browser, the date time offset can be captured easily using javascript.

1
var timeZoneOffset = -new Date().getTimezoneOffset(); // In minutes.

The time zone offset value is also needed on the server side in order to adjust time values before displaying them to the end user. A good place to store this offset is in a cookie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function cookieExists(name) {
    var nameToFind = name + "=";
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
        if (cookies[i].trim().indexOf(nameToFind) === 0) return true;
    }
    return false;
}
if (!cookieExists("_timeZoneOffset")) {
    var now = new Date();
    var timeZoneOffset = -now.getTimezoneOffset();  // in minutes
    now.setTime(now.getTime() + 10*24*60*60*1000); // keep it for 10 days
    document.cookie = "_timeZoneOffset=" + timeZoneOffset.toString()
                  + ";expires=" + now.toGMTString() + ";path=/;" + document.cookie;
   
    // Uncomment the following line to force page refresh. 
    // window.location.reload();
    }

The previous javascript code should always be always executed, so it would be a good idea to put this code in a common place such as _Layout.cshtml

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE HTML>
<html lang="@CultureHelper.GetCurrentNeutralCulture()" dir="@(CultureHelper.IsRightToLeft() ? " rtl"=rtl" = ="ltr" )"=)">
<head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>@ViewBag.Title - ASP.NET MVC Internationalization</title>
    @Styles.Render("~/Content/css" + (CultureHelper.IsRightToLeft() ? "-rtl" : ""))
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
        <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
        <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("ASP.NET MVC Internationalization", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
        <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav">                   
                </ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
        <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>          
        </footer>
    </div>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap" + (CultureHelper.IsRightToLeft() ? "-rtl" : ""))
    @RenderSection("scripts", required: false)
        <script type="text/javascript">
            function cookieExists(name) {
                var nameToFind = name + "=";
                var cookies = document.cookie.split(';');
                for (var i = 0; i < cookies.length; i++) {
                    if (cookies[i].trim().indexOf(nameToFind) === 0) return true;
                }
                return false;
            }
            if (!cookieExists("_timeZoneOffset")) {
                var now = new Date();
                var timeZoneOffset = -now.getTimezoneOffset();  // in minutes
                now.setTime(now.getTime() + 10*24*60*60*1000); // keep it for 10 days
                document.cookie = "_timeZoneOffset=" + timeZoneOffset.toString() + ";expires=" + now.toGMTString() + ";path=/;" + document.cookie;
                @* Uncomment the following line to force page refresh.  *@
                // window.location.reload();
                }
    </script>
</body>
</html>

 

In order to convert a DateTime to the user local time, an extension method ToOffset is needed.ToOffset should accept the time zone offset value.

1
2
3
4
5
6
7
8
9
10
/// <summary>
/// Converts the value of the current DateTime object to the date and time specified by an offset value.
/// </summary>
/// <param name="dt" />DateTime value.</param>
/// <param name="offset" />The offset to convert the DateTime value to.</param>
/// <returns>DateTime value that is local to an offset.</returns>
public static DateTime ToOffset (this DateTime dt, TimeSpan offset)
{
    return dt.ToUniversalTime().Add(offset);
}

DateTimeOffset already provides a similar method, so there is no need to implement it.

Parse the time zone offset value from the cookie in the base controller, and store it somewhere so it can be used later on the server side.

 

1
2
3
4
5
6
7
8
9
10
11
// Parse TimeZoneOffset.
ViewBag.TimeZoneOffset = TimeSpan.FromMinutes(0); // Default offset (Utc) if cookie is missing.
var timeZoneCookie = Request.Cookies["_timeZoneOffset"];
if (timeZoneCookie != null) {
            
    double offsetMinutes = 0;
    if (double.TryParse(timeZoneCookie.Value, out offsetMinutes)) {
        // Store in ViewBag. You can use Session, TempData, or anything else.
        ViewBag.TimeZoneOffset = TimeSpan.FromMinutes(offsetMinutes);
    }
}

 

The following is a model that illustrates how the date and time display is affected by different time zones.

 

1
2
3
4
5
6
7
8
9
10
11
[HttpGet]
public ActionResult Index()
{
    // Sample data to show dates and times in different places in the world.
    var model = new Collection<DateTimeOffset> {
        DateTimeOffset.Parse("2014-04-04T20:30:00-7:00"),
        DateTimeOffset.Parse("2014-04-01T11:00:00-2:00"),
        DateTimeOffset.Parse("2014-04-02T00:00:00+2:00"),
    };
    return View(model);
}

 

I used DateTimeOffset for illustrative purpose only, DateTime would work just fine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@model IEnumerable<DateTimeOffset>
@using MvcInternationalization.Extensions
@{
    ViewBag.Title = "Date and Time";
    var culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name.ToLowerInvariant();
}
@helper selected(string c, string culture)
{
    if (c == culture)
    {
        @:checked="checked"
    }
}
 
@using(Html.BeginForm("SetCulture", "Home"))
{
    <fieldset>
        <legend>@Resources.ChooseYourLanguage</legend>
        <div class="control-group">
            <div class="controls">
                <label for="en-us">
                    <input name="culture" id="en-us" value="en-us" type="radio" @selected("en-us", culture) /> English
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="es">
                    <input name="culture" id="es" value="es" type="radio" @selected("es", culture) /> Español
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="ar">
                    <input name="culture" id="ar" value="ar" type="radio" @selected("ar", culture) /> العربية
                </label>
            </div>
        </div>
      
    </fieldset>
}
     <div>
         <table class="table table-bordered table-hover table-striped">
             <thead>
                 <tr>
                     <th>UTC</th>
                     <th>Server</th>
                     <th>Local <span style="color: lightgray;"> - Your browser: @ViewBag.TimeZoneOffset</span></th>
                 </tr>
             </thead>
             <tbody>
                 @foreach (DateTimeOffset dto in Model) {
                     <tr>
                         <td>@dto.ToUniversalTime().ToString("g")</td>
                         <td>@dto.ToLocalTime().ToString("g")</td> @* You might be surprised that this value is actually local to the machine currently running. *@
                         <td>@dto.ToOffset(ViewBag.TimeZoneOffset).ToString("g")  </td>
                     </tr>
                 }
             </tbody>
         </table>
     </div> 
    
    
 
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">
        (function ($) {
            $("input[type = 'radio']").click(function () {
                $(this).parents("form").submit(); // post form
            });           
            
        })(jQuery);
    </script>
}

original article at :http://afana.me/post/aspnet-mvc-internationalization-date-time.aspx

Attribute Routing in ASP.NET MVC 5

Why Attribute Routing?

For example, a socially enhanced e-commerce website could have the following routes:

  • {productId:int}/{productTitle}
    Mapped to ProductsController.Show(int id)
  • {username}
    Mapped to ProfilesController.Show(string username)
  • {username}/catalogs/{catalogId:int}/{catalogTitle}
    Mapped to CatalogsController.Show(string username, int catalogId)

(Don’t mind the specific syntax right now, we will touch on this later.)

In previous version of ASP.NET MVC, the rules would be set in the RouteConfig.cs file, and point to the actual controller actions, as such:

  • routes.MapRoute(
  •     name: “ProductPage”,
  •     url: “{productId}/{productTitle}”,
  •     defaults: new { controller = “Products”, action = “Show” },
  •     constraints: new { productId = “\\d+” }
  • );

When the route definitions are co-located with the actions, within the same source file rather than being declared on an external configuration class, it can make it easier to reason about the mapping between URIs and actions. The previous route definition would be set using the following, simple attribute:

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

Enabling Attribute Routing

To enable attribute routing, call MapMvcAttributeRoutes during configuration.

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

You can also combine attribute routing with convention-based routing.

  • public static void RegisterRoutes(RouteCollection routes)
  • {
  •     routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
  •     routes.MapMvcAttributeRoutes();
  •     routes.MapRoute(
  •         name: “Default”,
  •         url: “{controller}/{action}/{id}”,
  •         defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }
  •     );
  • }

Optional URI Parameters and Default Values

You can make a URI parameter optional by adding a question mark to the route parameter. You can also specify a default value by using the form parameter=value.

  • public class BooksController : Controller
  • {
  •     // eg: /books
  •     // eg: /books/1430210079
  •     [Route(“books/{isbn?}”)]
  •     public ActionResult View(string isbn)
  •     {
  •         if (!String.IsNullOrEmpty(isbn))
  •         {
  •             return View(“OneBook”, GetBook(isbn));
  •         }
  •         return View(“AllBooks”, GetBooks());
  •     }
  •     // eg: /books/lang
  •     // eg: /books/lang/en
  •     // eg: /books/lang/he
  •     [Route(“books/lang/{lang=en}”)]
  •     public ActionResult ViewByLanguage(string lang)
  •     {
  •         return View(“OneBook”, GetBooksByLanguage(lang));
  •     }
  • }

In this example, both /books and /books/1430210079 will route to the “View” action, the former will result with listing all books, and the latter will list the specific book. Both /books/lang and /books/lang/en will be treated the same.

Route Prefixes

Often, the routes in a controller all start with the same prefix. For example:

  • public class ReviewsController : Controller
  • {
  •     // eg: /reviews
  •     [Route(“reviews”)]
  •     public ActionResult Index() { … }
  •     // eg: /reviews/5
  •     [Route(“reviews/{reviewId}”)]
  •     public ActionResult Show(int reviewId) { … }
  •     // eg: /reviews/5/edit
  •     [Route(“reviews/{reviewId}/edit”)]
  •     public ActionResult Edit(int reviewId) { … }
  • }

You can set a common prefix for an entire controller by using the [RoutePrefix] attribute:

  • [RoutePrefix(“reviews”)]
  • public class ReviewsController : Controller
  • {
  •     // eg.: /reviews
  •     [Route]
  •     public ActionResult Index() { … }
  •     // eg.: /reviews/5
  •     [Route(“{reviewId}”)]
  •     public ActionResult Show(int reviewId) { … }
  •     // eg.: /reviews/5/edit
  •     [Route(“{reviewId}/edit”)]
  •     public ActionResult Edit(int reviewId) { … }
  • }

Use a tilde (~) on the method attribute to override the route prefix if needed:

  • [RoutePrefix(“reviews”)]
  • public class ReviewsController : Controller
  • {
  •     // eg.: /spotlight-review
  •     [Route(“~/spotlight-review”)]
  •     public ActionResult ShowSpotlight() { … }
  •     …
  • }

Default Route

You can also apply the [Route] attribute on the controller level, capturing the action as a parameter. That route would then be applied on all actions in the controller, unless a specific [Route] has been defined on a specific action, overriding the default set on the controller.

  • [RoutePrefix(“promotions”)]
  • [Route(“{action=index}”)]
  • public class ReviewsController : Controller
  • {
  •     // eg.: /promotions
  •     public ActionResult Index() { … }
  •     // eg.: /promotions/archive
  •     public ActionResult Archive() { … }
  •     // eg.: /promotions/new
  •     public ActionResult New() { … }
  •     // eg.: /promotions/edit/5
  •     [Route(“edit/{promoId:int}”)]
  •     public ActionResult Edit(int promoId) { … }
  • }

Route Constraints

Route constraints let you restrict how the parameters in the route template are matched. The general syntax is{parameter:constraint}. For example:

  • // eg: /users/5
  • [Route(“users/{id:int}”]
  • public ActionResult GetUserById(int id) { … }
  • // eg: users/ken
  • [Route(“users/{name}”]
  • public ActionResult GetUserByName(string name) { … }

Here, the first route will only be selected if the “id” segment of the URI is an integer. Otherwise, the second route will be chosen.

The following table lists the constraints that are supported.

 

Constraint Description Example
alpha Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) {x:alpha}
bool Matches a Boolean value. {x:bool}
datetime Matches a DateTime value. {x:datetime}
decimal Matches a decimal value. {x:decimal}
double Matches a 64-bit floating-point value. {x:double}
float Matches a 32-bit floating-point value. {x:float}
guid Matches a GUID value. {x:guid}
int Matches a 32-bit integer value. {x:int}
length Matches a string with the specified length or within a specified range of lengths. {x:length(6)}
{x:length(1,20)}
long Matches a 64-bit integer value. {x:long}
max Matches an integer with a maximum value. {x:max(10)}
maxlength Matches a string with a maximum length. {x:maxlength(10)}
min Matches an integer with a minimum value. {x:min(10)}
minlength Matches a string with a minimum length. {x:minlength(10)}
range Matches an integer within a range of values. {x:range(10,50)}
regex Matches a regular expression. {x:regex(^\d{3}-\d{3}-\d{4}$)}

Notice that some of the constraints, such as “min”, take arguments in parentheses.

You can apply multiple constraints to a parameter, separated by a colon, for example:

  • // eg: /users/5
    // but not /users/10000000000 because it is larger than int.MaxValue,
    // and not /users/0 because of the min(1) constraint.
  • [Route(“users/{id:int:min(1)}”)]
  • public ActionResult GetUserById(int id) { … }

 

Specifying that a parameter is Optional (via the ‘?’ modifier) should be done after inline constraints:

 

  • // eg: /greetings/bye
    // and /greetings because of the Optional modifier,
    // but not /greetings/see-you-tomorrow because of the maxlength(3) constraint.
    [Route(“greetings/{message:maxlength(3)?}”)]
  • public ActionResult Greet(string message) { … }
Custom Route Constraints

You can create custom route constraints by implementing the IRouteConstraint interface. For example, the following constraint restricts a parameter to set of valid values:

  • public class ValuesConstraint : IRouteConstraint
  • {
  •     private readonly string[] validOptions;
  •     public ValuesConstraint(string options)
  •     {
  •         validOptions = options.Split(‘|’);
  •     }
  •     public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
  •     {
  •         object value;
  •         if (values.TryGetValue(parameterName, out value) && value != null)
  •         {
  •             return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
  •         }
  •         return false;
  •     }
  • }

The following code shows how to register the constraint:

  • public class RouteConfig
  • {
  •     public static void RegisterRoutes(RouteCollection routes)
  •     {
  •         routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
  •         var constraintsResolver = new DefaultInlineConstraintResolver();
  •         constraintsResolver.ConstraintMap.Add(“values”, typeof(ValuesConstraint));
  •         routes.MapMvcAttributeRoutes(constraintsResolver);
  •     }
  • }

Now you can apply the constraint in your routes:

  • public class TemperatureController : Controller
  • {
  •     // eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin
  •     [Route(“temp/{scale:values(celsius|fahrenheit)}”)]
  •     public ActionResult Show(string scale)
  •     {
  •         return Content(“scale is ” + scale);
  •     }
  • }

Route Names

You can specify a name for a route, in order to easily allow URI generation for it. For example, for the following route:

  • [Route(“menu”, Name = “mainmenu”)]
  • public ActionResult MainMenu() { … }

you could generate a link using Url.RouteUrl:

  • <a href=”@Url.RouteUrl(“mainmenu”)”>Main menu</a>

Areas

You can define that a controller belongs to an area by using the [RouteArea] attribute. When doing so, you can safely remove the AreaRegistration class for that area.

  • [RouteArea(“Admin”)]
  • [RoutePrefix(“menu”)]
  • [Route(“{action}”)]
  • public class MenuController : Controller
  • {
  •     // eg: /admin/menu/login
  •     public ActionResult Login() { … }
  •     // eg: /admin/menu/show-options
  •     [Route(“show-options”)]
  •     public ActionResult Options() { … }
  •     // eg: /stats
  •     [Route(“~/stats”)]
  •     public ActionResult Stats() { … }
  • }

With this controller, the following link generation call will result with the string “/Admin/menu/show-options

  • Url.Action(“Options”, “Menu”, new { Area = “Admin” })

You can set up a custom prefix for the area that defer from the area name, by using the AreaPrefix named parameter, for example:

  • [RouteArea(“BackOffice”, AreaPrefix = “back-office”)]

If you are using both Areas with route attributes, and areas with convention based routes (set by an AreaRegistration class), then you need to make sure that area registration happen after MVC attribute routes are configured, however before the default convention-based route is set. The reason is that route registration should be ordered from the most specific (attributes) through more general (area registration) to the mist generic (the default route) to avoid generic routes from “hiding” more specific routes by matching incoming requests too early in the pipeline.

Example:

  • public static void RegisterRoutes(RouteCollection routes)
  • {
  •     routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
  •     routes.MapMvcAttributeRoutes();
  •     AreaRegistration.RegisterAllAreas();
  •     routes.MapRoute(
  •         name: “Default”,
  •         url: “{controller}/{action}/{id}”,
  •         defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }
  •     );
  • }
  • taken from:http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx

Using Bootstrap Tooltips to display jQuery Validation error messages

Using Bootstrap Tooltips to display jQuery Validation error messages

I love jQuery Validation. I was recently putting together a screen which had a lot of different bits of validation going on. And the default jQuery Validation approach of displaying the validation messages next to the element being validated wasn’t working for me. That is to say, because of the amount of elements on the form, the appearance of validation messages was really making a mess of the presentation. So what to do?

<!DOCTYPE 2014-01-03_1355html>
<html lang=”en”>
<head>
<meta charset=”utf-8″ />
<link href=”//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css” rel=”stylesheet”>
<style>
form { padding: 10px; }
.error { border: 1px solid #b94a48!important; background-color: #fee!important; }
</style>
</head>
<body>
<form>
<div class=”row”>
<label for=”RequiredDateDemo”>A date is required (eg “15 June 2012”):</label>
<input
data-msg-date=”The field RequiredDateDemo must be a date.”
data-msg-required=”The RequiredDateDemo field is required.”
data-rule-date=”true”
data-rule-required=”true”
id=”RequiredDateDemo” name=”RequiredDateDemo” type=”text” value=”” />
</div>
<div class=”row”>
<label for=”StringLengthAndRequiredDemo”>A string is required between 5 and 10 characters long:</label>
<input
data-msg-maxlength=”The field StringLengthAndRequiredDemo must be a string with a minimum length of 5 and a maximum length of 10.”
data-msg-minlength=”The field StringLengthAndRequiredDemo must be a string with a minimum length of 5 and a maximum length of 10.”
data-msg-required=”The StringLengthAndRequiredDemo field is required.”
data-rule-maxlength=”10″
data-rule-minlength=”5″
data-rule-required=”true”
id=”StringLengthAndRequiredDemo” name=”StringLengthAndRequiredDemo” type=”text” value=”” />
</div>
<div class=”row”>
<label for=”RangeAndNumberDemo”>Must be a number between -20 and 40:</label>
<input
data-msg-number=”The field RangeAndNumberDemo must be a number.”
data-msg-range=”The field RangeAndNumberDemo must be between -20 and 40.”
data-rule-number=”true”
data-rule-range=”[-20,40]”
id=”RangeAndNumberDemo” name=”RangeAndNumberDemo” type=”text” value=”-21″ />
</div>
<div class=”row”>
<label for=”RangeAndNumberDemo”>An option must be selected:</label>
<select
data-msg-required=”The DropDownRequiredDemo field is required.”
data-rule-required=”true”
id=”DropDownRequiredDemo” name=”DropDownRequiredDemo”>
<option value=””>Please select</option>
<option value=”An Option”>An Option</option>
</select>
</div>
<div class=”row”>
<button type=”submit”>Validate</button>
</div>
</form>
<script src=”//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js” type=”text/javascript”></script>
<script src=”//ajax.aspnetcdn.com/ajax/jQuery.validate/1.11.1/jquery.validate.js” type=”text/javascript”></script>
<script src=”//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js”></script>
<script type=”text/javascript”>
$(“form”).validate({
showErrors: function(errorMap, errorList) {
// Clean up any tooltips for valid elements
$.each(this.validElements(), function (index, element) {
var $element = $(element);
$element.data(“title”, “”) // Clear the title – there is no error associated anymore
.removeClass(“error”)
.tooltip(“destroy”);
});
// Create new tooltips for invalid elements
$.each(errorList, function (index, error) {
var $element = $(error.element);
$element.tooltip(“destroy”) // Destroy any pre-existing tooltip so we can repopulate with new tooltip content
.data(“title”, error.message)
.addClass(“error”)
.tooltip(); // Create a new tooltip based on the error messsage we just set in the title
});
},
submitHandler: function(form) {
alert(“This is a valid form!”);
}
});
</script>
</body>
</html>

ASP.Net MVC Validation Using Fluent Validation

This article explains how to implement ASP.NET MVC validation using Fluent Validation. Fluent validation is a small validation library for .NET that uses a Fluent interface and lambda expressions for building validation rules for your business objects.  Fluent validation is one way of setting up dedicated validator objects, that you would use when you want to treat validation logic as separate from business logic. The Aspect-Oriented Programming (AOP) paradigm enables separation of cross-cutting concerns within a system, and validation is one such concern. Separating validation helps clean up your domain code and make it more cohesive, as well as giving you a single place to look for validation logic.

I create a Customer model (Customer class under Models folder) that has two properties, one is “Email” and another is “Name” as in the following code snippet:

namespace MvcValidation.Models

{

    public class Customer

    {

        public string Name { get; set; }

        public string Email { get; set; }

    }

}

Now I install the Fluent validation Nuget package in the application so I can use Fluent validation rules.

Figure 1.1 Install FluentValidation NuGet package

After that I create a validator class for the Customer model under the “Validators” folder but you can create it anywhere in an application for validation rules on Customer properties. I used two rules, one is not empty and another validates an email address.

using FluentValidation;

using MvcValidation.Models;

 

namespace MvcValidation.Validators

{

    public class CustomerValidator : AbstractValidator<Customer>

    {

        public CustomerValidator()

        {

            RuleFor(x => x.Name).NotEmpty().WithMessage(“Name is required”);

            RuleFor(x => x.Email).NotEmpty().WithMessage(“Email is required”);

            RuleFor(x => x.Email).EmailAddress().WithMessage(“Email is not valid”);               

        }

    }

}

After that, you need to create a controller’s action methods; these render views on the UI and binds a model with the view. So let’s create a controller with two action methods; these handle both request types (GET and POST) respectively.

using System.Web.Mvc;

using FluentValidation.Results;

using MvcValidation.Models;

using MvcValidation.Validators;

 

namespace MvcValidation.Controllers

{

    public class CustomerController : Controller

    {

        public ActionResult Index()

        {

            return View();

        }

        [AcceptVerbs(HttpVerbs.Post)]

        public ActionResult Index(Customer model)

        {

            CustomerValidator validator = new CustomerValidator();

            ValidationResult result = validator.Validate(model);

            if (result.IsValid)

            {

                ViewBag.Name = model.Name;

                ViewBag.Email = model.Email;

            }

            else

            {

                foreach (ValidationFailure failer in result.Errors)

                {

                    ModelState.AddModelError(failer.PropertyName, failer.ErrorMessage);

                }               

            }

            return View(model);

        }

    }

}

Thereafter I create a view (Index.cshtml) for user input under the Customer folder.

@model MvcValidation.Models.Customer

@{

    ViewBag.Title = “Index”;

}

@if (ViewData.ModelState.IsValid)

{

    <b>Name : @ViewBag.Name<br />

        Email : @ViewBag.Email

    </b>       

}

@using (Html.BeginForm())

{    

    <fieldset>

        <legend>Customer</legend>

        <div class=”editor-label”>

            @Html.LabelFor(model => model.Name)

        </div>

        <div class=”editor-field”>

            @Html.EditorFor(model => model.Name)

            @Html.ValidationMessageFor(model => model.Name)

        </div>

        <div class=”editor-label”>

            @Html.LabelFor(model => model.Email)

        </div>

        <div class=”editor-field”>

            @Html.EditorFor(model => model.Email)

            @Html.ValidationMessageFor(model => model.Email)

        </div>

        <p>

            <input type=”submit” value=”Create” />

        </p>

    </fieldset>

}

@section Scripts {

    @Scripts.Render(“~/bundles/jqueryval”)

}


Let’s run the application and test the following scenario:

1. When all fields are empty:

Figure 1.2: Validation Message when both fields are empty

2. When the Name field is empty but Email is not valid:

Figure 1.3 : Validation Message when Email is not valid

3. When both fields are valid:

Figure 1.4 All fields are valid

ASP.NET MVC – Legacy Url Routing

  1. A legacy Url is requested from your site. For example, http://www.server.com/Users/Login.aspx
  2. ASP.NET routing intercepts the request and matches a route from your route collection
  3. Instead of using the MvcRouteHandler, a LegacyRouteHandler is invoked.
  4. Using the LegacyRouteHandler, it’ll use the route redirection name you specified, generate the MVC Url, and issue a HTTP 301 with the location of http://www.server.com/site/login.

Routing

First, we should define our legacy route class. This is necessary because we need to expose an additional property to enable our routing handler to find the correct MVC route.

   1: // The legacy route class that exposes a RedirectActionName
   2: public class LegacyRoute : Route {
   3:     public LegacyRoute(string url, string redirectActionName, IRouteHandler routeHandler)
   4:         : base(url, routeHandler)
   5:     {
   6:         RedirectActionName = redirectActionName;
   7:     }
   8:
   9:     public string RedirectActionName { get; set; }
  10: }

Secondly, we need to define the route handler and associated http handler. The route handler derives from IRouteHandler, and will be the class used when creating your legacy routing. The http handler derives from MvcHandler because it gives us some critical information, like RequestContext. You’ll also notice that (while not in the code) you need to copy all of the querystring parameters from the request over. This is a necessary step because the GetVirtualPath method call will take all route data (from RouteData.Values) and try and utilize that when building the Url itself.

   1: // The legacy route handler, used for getting the HttpHandler for the request
   2: public class LegacyRouteHandler : IRouteHandler {
   3:     public IHttpHandler GetHttpHandler(RequestContext requestContext) {
   4:         return new LegacyHandler(requestContext)
   5:     }
   6: }
   7:
   8: // The legacy HttpHandler that handles the request
   9: public class LegacyHandler : MvcHandler {
  10:     public LegacyHandler(RequestContext requestContext) : base(requestContext) { }
  11:
  12:     protected override void ProcessRequest(HttpContextBase httpContext) {
  13:         string redirectActionName = ((LegacyRoute)RequestContext.RouteData.Route).RedirectActionName;
  14:
  15:         // ... copy all of the querystring parameters and put them within RouteContext.RouteData.Values
  16:
  17:         VirtualPathData data = RouteTable.Routes.GetVirtualPath(RouteContext, redirectActionName, RouteContext.RouteData.Values);
  18:
  19:         httpContext.Status = "301 Moved Permanently";
  20:         httpContext.AppendHeader("Location", data.VirtualPath);
  21:     }
  22: }

Lastly, you need to create your routes within the Global.asax file. Remember, that order is necessary when setting up routing.

   1: public void RegisterRoutes(RouteCollection routes) {
   2:     routes.MapRoute("Login", "site/login", new {
   3:         controller = "Users",
   4:         action = "DisplayLogin"
   5:     });
   6:
   7:     routes.Add("", new LegacyRoute(
   8:         "Users/Login.aspx",
   9:         "Login",
  10:         new LegacyRouteHandler()));
  11: }

And that’s it. When a request comes in, you’ll see the following in Fiddler

  1. A request on “Users/Login.aspx”
  2. A HTTP 301, with a header “Location” and value of “site/login”
  3. A request on “site/login”

Final Thoughts

Granted, there’s more you can do with this – like creating your own extension methods like MapRoute and doing better handling of finding the route, but this should get you started. Also, I’m writing the code off the top of my head, so there’s no guarantee that any of it works as-is. Please let me know if you have any other thoughts.

 

Lastly, for those wondering why are we using a HTTP 301 status code? Well read up on them. “301 Moved Permanently” indicates “that all future requests should be directed to the given URI.” While your end users will not see any difference other than a new URL in the browser, the 301 status code more aimed towards search engines to update their URL’s in their indexes.

original article at:http://www.eworldui.net/blog/post/2008/04/ASPNET-MVC—Legacy-Url-Routing.aspx

301 redirects

While ASP.NET MVC has a powerful routing engine for handling requests, there can be a scenario when you need to process a list of specific URL rewrites in your application. Often this can be due to a new version of an existing site going live where the URL structure has changed. Under Apache, this can be handled easily in the .htaccess file of the site by listing out each path  you’d like to rewrite like so:

1
2
3
4
<IfModule mod_rewrite.c>
  RewriteEngine on
  Redirect 301 /somepage.html /otherpage.html
</IfModule>

It can also be handled in IIS 7 using the URL Rewrite module or under IIS 6 using a 3rd party extension such as Helicon ISAPI Rewrite.

But what if you want to handle the redirects via code?

Bulk 301 Redirect – Considerations

Our two main considerations when coming up with this solution were:

  1. We don’t want to check for a redirect on every request (just old broken links)*
  2. We don’t want to have to do a code push to change the 301 mapping list

*: You can use the same solution to process every request by changing the location of the 301 check

This led us down a path where we are capturing 404 requests in the application, then checking for a rewrite rule before actually displaying a 404 page. If the rule exists, we rewrite the path instead of showing the 404.

The 301 List

With consideration #2 in mind, we decided to use a flat file in the App_Data folder to hold our rewrite rules. The file is simple since all we are concerned with is permanent redirects. We use a plain old CSV file with 2 columns, the first column being the old path to check for, the second column is the new path to permanently redirect to. For example:

1
2
/Home/FollowUs,/About/Follow-Us
/Home/ContactUs,/About/contact-us

This makes constructing, updating, and maintaining the rewrite list extremely easy. Even better, since it’s a CSV file, you can use excel to have a non technical person create the list for you if it’s lengthy.

To process the list, we created a single static method to load the list into a key,value Dictionary object. Here is the entire class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
namespace CypressNorth.Data.Services
{
    public class FlatFileAccess
    {
        public static Dictionary<string,string> Read301CSV()
        {
            string file = "App_Data/301.csv";
            string path = System.IO.Path.Combine(AppDomain.CurrentDomain.GetData("DataDirectory").ToString(), file);
            if (File.Exists(path))
            {
                using (TextReader sr = new StreamReader(path))
                {
                    Dictionary<string,string> redirect_dict = new Dictionary<string,string>();
                    string line = "";
                    while ((line = sr.ReadLine()) != null)
                    {
                        string[] columns = line.Split(',');
                        redirect_dict.Add(columns[0], columns[1]);
                    }
                    return redirect_dict;
                }
            }
            else
                return null;
        }
    }
}

This will process your list of rewrite rules into a Dictionary<string,string> list and return it for you.

Intercepting 404 responses

Now that we have our list of 301 redirects and a way to access them, we need to hook into the app request life cycle to process them. Our consideration #1 states that we don’t want to process every single request to the site, just bad URL’s so the logical place for this is when a 404 response is encountered.

To hook into this we create a new method in the global.asax file. You could do this via an HttpModule as well if you prefer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void Application_Error()
{
    Exception exception = Server.GetLastError();
    // Clear the error
    Response.Clear();
    Server.ClearError();
    if (exception is HttpException)
    {
        HttpException ex = exception as HttpException;
        if(ex.GetHttpCode() == 404)
        {
            var url = HttpContext.Current.Request.Path.TrimEnd('/');
            var redirect = FlatFileAccess.Read301CSV();
            if (redirect.Keys.Contains(url))
            {
                // 301 it to the new url
                Response.RedirectPermanent(redirect[url]);
            }
        }
    }
}

This method will catch Exceptions application wide. You can implement much more robust error handing using the same hook but for our purposes we just want to catch 404′s and check for redirects. So when an error is thrown in the application, the exception is checked to see if it is a 404 type HttpException. If it is, we load the 301.csv file and compare it against the requested path. If a match is found we 301 redirect the request to the new url. If it is not found, we let the error fall through.

ASP.NET Web API 2.1 RC is out

The ASP.NET team kept it’s frantic work pace and already before Christmas dropped onNuget a 2.1 RC of Web API (as well as new MVC and Web Pages).

While in the open source mode of operation (public commits, nightly builds) this is that big of a news, it is still a very nice piece of news. Let’s look at what’s new.

Overview of ASP.NET Web API 2.1 RC

The official release notes are available here. Most of the features you have already seen – as on this blog we try to cover both existing and the upcoming bits of the Web API framework.

You can grab the release from Nuget:

Shell
1
Install-Package Microsoft.AspNet.WebApi -Pre

 

Glboal error handling

Arguably the biggest feature of this drop – something that has been very heavily discussed i nthe advisors meetings and in the discussion list.

The full feature specification is available here.

Now, in short, you get a single point of error handling for your Web API – something that didn’t exist before, and trying to handle all errors required you to catch in many places (or resort toGlobal.asax).

Up to this point you could use ExceptionFilters for more or less widely-scoped error handling; but they wouldn’t catch exceptions from message handlers, media type formatters or routing.

The simplest example is absolutely trivial:

C#
1
2
3
4
5
6
7
public class TraceExceptionLogger : ExceptionLogger
{
    public override void LogCore(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

 

BSON formatter

Web API now supports application/bson out of the box. We have written a BSON formatter on this blog back in the summer of 2012 and it’s also a part of WebApiContrib, but it’s nice to have it in the core, especially as it doesn’t bring in any external dependencies (it relies on JSON.NET).

It has also been hardedned a bit, so that’s another plus.

Async filters

I have just recently blogged about async filters for Web API. As mentioned there, the vNext will have it in the box, and as a result, you can enjoy this feature in Web API 2.1 RC.

Ignore routes

HttpRoutesCollection will now support IgnoreRoute mechanism, allowing you to easily exclude parts of your routing hierarchy from being matched.

Attribute routing enhancements

Attribute routing now provides new extensibility hooks. They are described in detail here (bottom of the page) and allow you to provide instructions on custom route resolution (IDirectRouteProvider). This is extremely useful when trying to do something like versioning or localization.

Fixes, fixes, fixes

Plenty of bug fixes too. Among the more interesting ones:

Web API 2’s Attribute Routing: Looking Deeper

While most of us are used to creating routes using the Web API configuration, attributed routes are different. Before attributed routing, when you would look at the route data (e.g. request.GetRouteData()) you would get a route with a name that tied to the configured name. This was really useful for the way to use UrlHelper to build your URLs.

In fact, you can get this behavior by supplying names to the individual routes in your attributed routing:

1
2
3
4
5
6
7
8
9
10
11
12
13
public FooController : ApiController
{
  [Route("api/foos", Name = "Foo")]
  public object Get()
  {
    // ...
  }
}
...
var helper = new UrlHelper(Request);
var url = helper.Link("Foo");

But if you’re traversing the route collection in any way (e.g. in a ControllerSelector) it is important to understand where these routes actually are. When you use attribute routing, all the route attributes get added to a common route without a name. This is a special route that is an instance of an internal class called RouteCollectionRoute (Source Link). This route has a collection of sub-routes that you can query for that includes *all* the attribute routes. But if you just want the selected route for your call, you can simple ask for it using the RouteData.Values:

1
2
3
var routeData = request.GetRouteData();
var subroutes = (IEnumerable<IHttpRouteData>)routeData.Values["MS_SubRoutes"];
var route = subroutes.First().Route;

The real problem for some is that there is no longer controller name in the route data. This makes sense of course because there is no specific controller that a route points to as attributed routes are related to methods, not controllers. Being aware of these internals may help you solve issues when you use or move to using attributed routes.