Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Friday, 24 January 2014

There are going to be in depth notes here from this article.  Please consider buying the whole pdf  from him (as I have) as it
http://imar.spaanjaars.com/573/aspnet-n-layered-applications-introduction-part-1

These are purely my notes Imar’s article.

My goals are:

  • Understand this architecture as it “can be used to build real-world, large scale web applications.”
  • Build up from scratch his sample
  • Build out my own Winter Project app with the concepts I like from here
  • Pragmatic
  • Simple

Why Use N-Layers

  • Testability
  • Separation – make it easier to find where things are in large projects eg dataaccess, business logic, UI
  • Reuse – eg a console app can use all the goodness of the main MVC app

image

Previous Versions

  • BLL/DAL – lots of pass through code
  • DAL tight coupling making hard to test
  • SP’s hard to maintain and test
  • Validation was custom – now have better ways

Sidenote: PDF reader being horrible.. trying: http://www.foxitsoftware.com/downloads/

Starting from Scratch

image
Updating all packages

Also have enabled NuGet Package Restore.

image
My DLayer version.

Put into source control – am using github.

| | # 
# Thursday, 19 December 2013
( MVC4 | MVC5 )

 

    <add key="MailServer" value="mail.server.com" />
  </appSettings>
ViewBag.MailServer = ConfigurationManager.AppSettings["MailServer"];
@ViewBag.MailServer

Connectionstring, FileShareNames, MailServer names

Deploying

image
setting up IIS on the deb box instead of IIS Express.

then IIS Management Console and SQL Express.  Using LocalDB from full IIS can be a pain.

sa password – System Administrator

AutomaticMigrationsEnabled=false;

Delete the _InitialCreate file in migrations.

Add-Migration InitialCreate

image

Right click on Web Project and select publish

image

image

image
As we’re doing migrations in code – Global.asax.cs don’t need to force via config.

image

/T – checks

/Y – does it.

image
On first load..

So it has actually created the database…

image
ahh – it was Anonymous Authentication was set to false..

image

image
Controllers / all C# code compiled into the OdeToFood.dll

Publish up to Azure and don’t need to check the code migrations box as am doing it in Global.asax

| | # 
# Wednesday, 18 December 2013
( EF6 | MVC4 | MVC5 )

Taken and modified from Scott Allen’s http://pluralsight.com/training/Courses/TableOfContents/mvc4-building

This is using MVC5 (with the new Identity provider), and AutomaticMigrationsEnabled.  I’m kept with 1 DbContext for now for the ease of auto migrations being able to do Update-Database without any parameters.

public interface IOdeToFoodDb : IDisposable
{
    IQueryable<T> Query<T>() where T : class;
    void Add<T>(T entity) where T : class;
    void Update<T>(T entity) where T : class;
    void Remove<T>(T entity) where T : class;
    void SaveChanges();
}

// Was ApplicationDbContext (which contains User and Role in MVC5)
public class OdeToFoodDb : IdentityDbContext<ApplicationUser>, IOdeToFoodDb
{
    public OdeToFoodDb()
        : base("DefaultConnection")
    { }

    public DbSet<Restaurant> Restaurants { get; set; }
    public DbSet<RestaurantReview> Reviews { get; set; }

    IQueryable<T> IOdeToFoodDb.Query<T>()
    {
        return Set<T>();
    }

    void IOdeToFoodDb.Add<T>(T entity)
    {
        Set<T>().Add(entity);
    }

    void IOdeToFoodDb.Update<T>(T entity)
    {
        Entry(entity).State = System.Data.Entity.EntityState.Modified;
    }

    void IOdeToFoodDb.Remove<T>(T entity)
    {
        Set<T>().Remove(entity);
    }

    void IOdeToFoodDb.SaveChanges()
    {
        SaveChanges();
    }
}

and calling

public class HomeController : Controller
{
    IOdeToFoodDb _db;

    public HomeController()
    {
        _db = new OdeToFoodDb();
    }

    public HomeController(IOdeToFoodDb db)
    {
        _db = db;
    }

    public ActionResult Index(string searchTerm = null, int page = 1)
    {
        var model = _db.Query<Restaurant>()
                        //.Restaurants
                        .OrderByDescending(r => r.Reviews.Average(review => review.Rating))
                        .Where(r => searchTerm == null || r.Name.StartsWith(searchTerm))
                        .Select(r => new RestaurantListViewModel
                                {
                                    Id = r.Id,
                                    Name = r.Name,
                                    City = r.City,
                                    Country = r.Country,
                                    NumberOfReviews = r.Reviews.Count()
                                })
                            .ToPagedList(page, 10);
        return View(model);
    }

primarily good for testing:

[TestMethod]
public void Index()
{
    // Arrange
    var db = new FakeOdeToFoodDb();
    db.AddSet(TestData.Restaurants);
    HomeController controller = new HomeController(db);
    // Was needed for a call in HomeController for isAjaxRequest.
    //controller.ControllerContext = new FakeControllerContext();

    // Act
    ViewResult result = controller.Index() as ViewResult;
    IEnumerable<RestaurantListViewModel> model = result.Model as IEnumerable<RestaurantListViewModel>;

    // Assert
    Assert.AreEqual(10, model.Count());
}

Injecting in a FakeDb

public class FakeOdeToFoodDb : IOdeToFoodDb
{
    public IQueryable<T> Query<T>() where T : class
    {
        return Sets[typeof(T)] as IQueryable<T>;
    }

    void IDisposable.Dispose() { }

    public void AddSet<T>(IQueryable<T> objects)
    {
        Sets.Add(typeof(T), objects);
    }

    public void Add<T>(T entity) where T : class
    {
        Added.Add(entity);
    }

    public void Update<T>(T entity) where T : class
    {
        Updated.Add(entity);
    }

    public void Remove<T>(T entity) where T : class
    {
        Removed.Add(entity);
    }

    public void SaveChanges()
    {
        Saved = true;
    }

    public Dictionary<Type, object> Sets = new Dictionary<Type, object>();
    public List<object> Added = new List<object>();
    public List<object> Updated = new List<object>();
    public List<object> Removed = new List<object>();
    public bool Saved = false;
}

The FakeDb which operates as an in-memory collection.

| | # 
# Monday, 16 December 2013
( MVC4 )

Making an in-memory fake

[TestClass]
public class RestaurantControllerTests
{
    [TestMethod]
    public void Create_Saves_Restaurant_When_Valid()
    {
        var db = new FakeApplicationDb();
        var controller = new RestaurantController(db);

        controller.Create(new Restaurant());

        Assert.AreEqual(1, db.Added.Count);
        Assert.AreEqual(true, db.Saved);
    }

    [TestMethod]
    public void Create_Does_Not_Save__Restaurant_When_Invalid()
    {
        var db = new FakeApplicationDb();
        var controller = new RestaurantController(db);
        controller.ModelState.AddModelError("", "Invalid");

        controller.Create(new Restaurant());

        Assert.AreEqual(0, db.Added.Count);
    }
}

Could also write tests for

  • controller results proper actionresult when it saves or redirects

   Could use include in Linq to get children .. more performance than the virtual way http://pluralsight.com/training/Courses/Discussion/mvc4-building

| | # 
( MVC4 | TDD )

Unit test controllers…isolate from Web, Mail, Database?

  • Could write Unit Tests that hit the db
    • downside is slower
    • downside is setup too
      • SQL Server compact is better
      • Transactions are good
  • Isolate

Extract an interface on DbContext

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

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IApplicationDbContext
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {}

    public DbSet<Restaurant> Restaurants { get; set; }
    public DbSet<RestaurantReview> Reviews { get; set; }

    IQueryable<T> IApplicationDbContext.Query<T>()
    {
        // If someone wants a Query<Restaurant> then this Set will work
        return Set<T>();
    }

    void IDisposable.Dispose()
    { }
}

Extracting a simple interface of Query<T>

public class HomeController : Controller
{
    IApplicationDbContext _db;

    public HomeController()
    {
        _db = new ApplicationDbContext();
    }

    public HomeController(IApplicationDbContext db)
    {
        _db = db;
    }

    public ActionResult Index(string searchTerm = null, int page = 1)
    {
        var model = _db.Query<Restaurant>()
                        //.Restaurants
                        .OrderByDescending(r => r.Reviews.Average(review => review.Rating))
                        .Where(r => searchTerm == null || r.Name.StartsWith(searchTerm))
                        .Select(r => new RestaurantListViewModel
                                {
                                    Id = r.Id,
                                    Name = r.Name,
                                    City = r.City,
                                    Country = r.Country,
                                    NumberOfReviews = r.Reviews.Count()
                                })
                            .ToPagedList(page, 10);
        return View(model);
    }

Simple DI, and adding in Query

[TestMethod]
public void Index()
{
    // Arrange
    var db = new FakeIApplicationDbContext();
    db.AddSet(TestData.Restaurants);
    HomeController controller = new HomeController(db);

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

    // Assert
    Assert.IsNotNull(result);
}

Passing in a Fake DbContext

namespace OdeToFood.Tests
{
    public class FakeIApplicationDbContext : IApplicationDbContext
    {
        public Dictionary<Type, object> Sets = new Dictionary<Type, object>();

        public IQueryable<T> Query<T>() where T : class
        {
            return Sets[typeof(T)] as IQueryable<T>;
        }

        public void AddSet<T>(IQueryable<T> objects)
        {
            Sets.Add(typeof(T), objects);
        }

        void IDisposable.Dispose() { }
    }
}

Generics laden!

And now a simple TestData generator

public class TestData
{
    public static IQueryable<Restaurant> Restaurants
    {
        get
        {
            var restaurants = new List<Restaurant>();
            for (int i = 0; i < 100; i++)
            {
                var restaurant = new Restaurant();
                restaurant.Reviews = new List<RestaurantReview>()
                {
                    new RestaurantReview {Rating=4}
                };
                restaurants.Add(restaurant);
            }
            return restaurants.AsQueryable();
        }
    }
}

Faking out HttpContext

eg if have an If(ajaxrequest)

    public class FakeIApplicationDbContext : IApplicationDbContext
    {
        public Dictionary<Type, object> Sets = new Dictionary<Type, object>();

        public IQueryable<T> Query<T>() where T : class
        {
            return Sets[typeof(T)] as IQueryable<T>;
        }

        public void AddSet<T>(IQueryable<T> objects)
        {
            Sets.Add(typeof(T), objects);
        }

        void IDisposable.Dispose() { }
    }

        [TestMethod]
        public void Index()
        {
            // Arrange
            var db = new FakeIApplicationDbContext();
            db.AddSet(TestData.Restaurants);
            HomeController controller = new HomeController(db);
            // Was needed for a call in HomeController for isAjaxRequest.
            controller.ControllerContext = new FakeControllerContext();

sdf

[TestMethod]
public void Index()
{
    // Arrange
    var db = new FakeIApplicationDbContext();
    db.AddSet(TestData.Restaurants);
    HomeController controller = new HomeController(db);
    // Was needed for a call in HomeController for isAjaxRequest.
    controller.ControllerContext = new FakeControllerContext();

    // Act
    ViewResult result = controller.Index() as ViewResult;
    IEnumerable<RestaurantListViewModel> model = result.Model as IEnumerable<RestaurantListViewModel>;

    // Assert
    Assert.AreEqual(10, model.Count());
}

Very useful pulling out the 10 that should be there… as the home controller passes in Take(10) of the 100 results that are in the test data.

| | # 
# Thursday, 12 December 2013
( MVC4 )

Ctrl R A – Run all tests

“I have found it to be the most beneficial practice I have ever followed in my career” Scott Allen

//
// A restaurant's overall rating can be caluclated using various methods.
// For this application we'll want to try different methods over time,
// but for starters we'll allow an administrator to toggle between two
// different techniques.
//
// 1. Simple mean of the "rating" value for the most recent n reviews
//    (the admin can configure the value n).
//
// 2. Weighted mean of the last n reviews. The most recent n/2 reviews
//    will be weighted twice as much and the oldest n/2 reviews.
//
// Overall rating should be a whole number.

Taking business logic and driving it out using TDD.
[TestMethod]
public void TestMethod1()
{
    var data = new Restaurant();
    data.Reviews = new List<RestaurantReview>();
    data.Reviews.Add(new RestaurantReview { Rating = 4 });

    var rater = new RestaurantRater(data);
    RatingResult result = rater.ComputeRating(10);

    Assert.AreEqual(4, result.Rating);
}

So added in new RestaurantRater class which has a method called ComputeRating which returns RatingResult.

Tip: Start typing and do Ctrl . to rename

[TestMethod]
public void Computes_Result_For_One_Review()
{
    var data = new Restaurant();
    data.Reviews = new List<RestaurantReview>();
    data.Reviews.Add(new RestaurantReview { Rating = 4 });

    var rater = new RestaurantRater(data);
    // Where 10 in the most recent n reviews to average over
    RatingResult result = rater.ComputeRating(10);

    Assert.AreEqual(4, result.Rating);
}

[TestMethod]
public void Computes_Result_For_Two_Reviews()
{
    var data = new Restaurant();
    data.Reviews = new List<RestaurantReview>();
    data.Reviews.Add(new RestaurantReview { Rating = 4 });
    data.Reviews.Add(new RestaurantReview { Rating = 8 });

    var rater = new RestaurantRater(data);
    // Where 10 in the most recent n reviews to average over
    RatingResult result = rater.ComputeRating(10);

    Assert.AreEqual(6, result.Rating);
}

Building out the tests and naming them (not sure I like his way)

public RatingResult ComputeRating(int numberOfReviews)
{
    var result = new RatingResult();

    //ICollection<RestaurantReview> listOfReviews = _restaurant.Reviews;
    //// Get average
    //int i = 0;
    //foreach (var item in listOfReviews)
    //{
    //    i += item.Rating;    
    //}
    //i = i / listOfReviews.Count();

    //result.Rating = i;
    result.Rating = (int)_restaurant.Reviews.Average(rev => rev.Rating);
    return result;
}

Working through the average method.

Builder

[TestMethod]
public void Computes_Result_For_Two_Reviews()
{
    var data = BuildRestaurantAndReviews(4, 8);

    var rater = new RestaurantRater(data);
    RatingResult result = rater.ComputeRating(10);

    Assert.AreEqual(6, result.Rating);
}

private Restaurant BuildRestaurantAndReviews(params int[] ratings)
{
    var restaurant = new Restaurant();
    //restaurant.Reviews = new List<RestaurantReview>();

    //foreach (var item in ratings)
    //{
    //    restaurant.Reviews.Add(new RestaurantReview { Rating = item });
    //}

    restaurant.Reviews =
        ratings.Select(r => new RestaurantReview { Rating = r })
                .ToList();
    return restaurant;

params is interesting

var data = BuildRestaurantAndReviews(new int[] { 4 });

if didn’t use params would have to use this.

ComputeWeightedResult

public RatingResult ComputeWeightedRate(int numberOfReviews)
{
    var reviews = _restaurant.Reviews.ToArray();
    var result = new RatingResult();
    var counter = 0;
    var total = 0;

    for (int i = 0; i < reviews.Count(); i++)
    {
        if (i < reviews.Count() / 2)
        {
            counter += 2;
            total += reviews[i].Rating * 2;
        }
        else
        {
            counter += 1;
            total += reviews[i].Rating;
        }
    }
    result.Rating = total / counter;
    return result;
}

Interesting conversion to an array – simple.

Refactoring

Want to declutter RestaurantRater, as business will want to change this all the time.  Want to make things easy to change algorithms.

Strategy Pattern

“strategy pattern (also known as the policy pattern) is a software design pattern, whereby an algorithm's behaviour can be selected at runtime”

class RestaurantRater
{
    private Restaurant _restaurant;

    public RestaurantRater(Restaurant restaurant)
    {
        this._restaurant = restaurant;
    }

    public RatingResult ComputeResult(IRatingAlgorithm algorithm, int numberOfReivewsToUse)
    {
        return algorithm.Compute(_restaurant.Reviews.ToList());
    }
}

The RestaurantRater is not responsible for computing the result.  It is now passed in an algorithm.

public interface IRatingAlgorithm
{
    RatingResult Compute(IList<RestaurantReview> reviews);
}

public class SimpleRatingAlgorithm : IRatingAlgorithm
{
    public RatingResult Compute(IList<RestaurantReview> reviews)
    {
        var result = new RatingResult();
        result.Rating = (int)reviews.Average(rev => rev.Rating);
        return result;
    }
}

public class WeightedRatingAlgorithm : IRatingAlgorithm
{
    public RatingResult Compute(IList<RestaurantReview> reviews)
    {
        var result = new RatingResult();
        var counter = 0;
        var total = 0;

        for (int i = 0; i < reviews.Count(); i++)
        {
            if (i < reviews.Count() / 2)
            {
                counter += 2;
                total += reviews[i].Rating * 2;
            }
            else
            {
                counter += 1;
                total += reviews[i].Rating;
            }
        }
        result.Rating = total / counter;
        return result;
    }
}

Why can’t we test the algorithms directly?

class RestaurantRater
{
    private Restaurant _restaurant;

    public RestaurantRater(Restaurant restaurant)
    {
        this._restaurant = restaurant;
    }

    public RatingResult ComputeResult(IRatingAlgorithm algorithm, int numberOfReivewsToUse)
    {
        var filteredReviews = _restaurant.Reviews.Take(numberOfReivewsToUse);

        return algorithm.Compute(filteredReviews.ToList());
    }
}

So now RestaurantRater has a purpose to massage the data to pass to the algorithm.

  • Rounding errors
  • Truncating errors
  • No reviews
  • Reviews sorted properly?
| | # 
( MVC4 )

Caching

[OutputCache] action filter

  • Do have to know where expensive pages/queries are.
[OutputCache(Duration=60)]
public class HomeController : Controller
{
    ApplicationDbContext _db = new ApplicationDbContext();

    // null for unit test helping
    public ActionResult Index(string searchTerm = null)
    {
        var model = _db.Restaurants
                        .OrderByDescending(r => r.Reviews.Average(review => review.Rating))
                        .Where(r => searchTerm == null || r.Name.StartsWith(searchTerm))
                        .Take(10)
                        .Select(r => new RestaurantListViewModel
                                {
                                    Id = r.Id,
                                    Name = r.Name,
                                    City = r.City,
                                    Country = r.Country,
                                    NumberOfReviews = r.Reviews.Count()
                                });
        return View(model);
    }

image
Caching of the ChildAction (which is Hello) for 60s, and the main page for 5s.

  • Useful to cache some things which are expensive in a ChildAction

VaryByParam – Usually want default..

Location – cache on server, client, client and server

VaryByHeader – language..

SqlDependency – sounds great but not widely used.

PagedLists

image

public ActionResult Index(string searchTerm = null, int page = 1)
{
    var model = _db.Restaurants
                    .OrderByDescending(r => r.Reviews.Average(review => review.Rating))
                    .Where(r => searchTerm == null || r.Name.StartsWith(searchTerm))
                    .Take(10)
                    .Select(r => new RestaurantListViewModel
                            {
                                Id = r.Id,
                                Name = r.Name,
                                City = r.City,
                                Country = r.Country,
                                NumberOfReviews = r.Reviews.Count()
                            })
                        .ToPagedList(page,10);
    return View(model);
}
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="OdeToFood" />
        <add namespace="PagedList" />
        <add namespace="PagedList.MVC" />
        <add namespace="OdeToFood.Models" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

Adding default namespaces to entire razor side of project in Views/Web.config.  Then restart VS2013.

image

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

Nice!

image
MinimalWithPageCountText

image
TwitterBootstrap aligned.. very nice.

image
TwitterBootstrapPager

FullRequest

problem is someone bookmarks..and have ajax request… VaryByHeader “X-Requested-With”

but browsers cache too. and some not smart enough

    [OutputCache(Duration = 60, Location = OutputCacheLocation.Server)]
    public class HomeController : Controller
    {
        ApplicationDbContext _db = new ApplicationDbContext();

Force caching only on server:

Cache Profiles

<system.web>
    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="Long" duration="300"/>
          <add name="Short" duration="5"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
[OutputCache(CacheProfile="Long", Location = OutputCacheLocation.Server)]
    public class HomeController : Controller
    {

Diagnostics

Just what is our application doing?

  • ASP.NET Health monitoring
  • Log4Net
  • ELMAH (Error logging Modules and Handlers)
  • P&P Logging block

Machine level health monitoring: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.config

Providers – EventLog, SQL Server

EventMappings – eg All Errors

Rules – defines where All Errors will go to windows event log.

image

image

Can email etc..

<appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="elmah.mvc.disableHandler" value="false" />
    <add key="elmah.mvc.disableHandleErrorFilter" value="false" />
    <add key="elmah.mvc.requiresAuthentication" value="true" /> <!--so need auth-->
    <add key="elmah.mvc.IgnoreDefaultRoute" value="false" />
    <add key="elmah.mvc.allowedRoles" value="Admin" /> <!--was * for all-->
    <add key="elmah.mvc.allowedUsers" value="*" />
    <add key="elmah.mvc.route" value="elmah" /> <!--the route to get here-->
  </appSettings>

Elmah setup

| | # 
( Ajax | MVC4 )

MVC4 will automatically minify.

jquery.validate

unobtrusive files are the bridges to MVC eg for validation.

Top of page

  • Styles.Render CSS
  • Scripts.Redner modernizr… important for things like IE6.

Bottom on page eg

  • jquery
  • bootstrap

Scripts.Render – bundles and minifys

public class BundleConfig
{
    // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/jquery.validate*"));

        // Use the development version of Modernizr to develop with and learn from. Then, when you're
        // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
        bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/modernizr-*"));

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

        bundles.Add(new StyleBundle("~/Content/css").Include(
                    "~/Content/bootstrap.css",
                    "~/Content/site.css"));
    }
}
http://stackoverflow.com/questions/20081328/how-to-add-jqueryui-library-in-mvc-5-project  - jQueryUI

Autocomplete ajax could be useful… but..sugar only for now.

| | # 
( MVC4 )

CRUD Restaurants

Scaffold up Restaurant controller from the Restaurant class and  DbContext we’ve created already.

image
Adding a partial view

image
Problem is that @Model.Reviews is null by default (ah, not loaded child collections).

    public class Restaurant
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public virtual ICollection<RestaurantReview> Reviews { get; set; }
    }

Easiest way to solve is to add virtual to the property.. EF will add a wrapper at runtime, it will load them up for you.  Will need a second query to pull in those reviews.

Could eager load to get down to 1 query.

[HttpPost]
public ActionResult Create(RestaurantReview review)
{
    if (ModelState.IsValid)
    {
        _db.Entry(review).State = EntityState.Modified;
        _db.SaveChanges();
        return RedirectToAction("Index", new { id = review.RestaurantId });
    }
    return View(review);
}

Interesting. Already in the db..  ie attach an existing review.

ModelBinding

Security related issues….ie if taking out reviewername out of form.. but could easily post stuff back. 

As the modelbinder just brute forces.. Overposting or MassAssignment.

ViewModels are probably better.

[HttpPost]
public ActionResult Edit([Bind(Exclude="ReviewerName")]RestaurantReview review)
{
    if (ModelState.IsValid)
    {
        _db.Entry(review).State = EntityState.Modified;
        _db.SaveChanges();
        return RedirectToAction("Index", new { id = review.RestaurantId });
    }
    return View(review);
}

Blacklist.

Data Annotations

image
update-database didn’t work first time as potential dataloss due to nvarchar(max) to nvarchar(1024)

update-database –verbose –force

image
Client side javascript works.  And works on server side too.

public class RestaurantReview
{
    public int Id { get; set; }

    [Range(1,10)]
    [Required] // Not necessary as int is value type
    public int Rating { get; set; }

    [Required]
    [StringLength(1024)]
    public string Body { get; set; }

    [Display(Name="User Name")]
    [DisplayFormat(NullDisplayText="anonymous")]
    public string ReviewerName { get; set; }

    // Not necessary, but makes some scenarios easier
    public int RestaurantId { get; set; }
}

Custom Validation

Restrict the max number of words a user can enter into a string property. http://hossamhassan47.wordpress.com/2012/12/09/mvc-4-0-how-to-support-client-side-custom-validation/

image

public class MaxWordsAttribute : ValidationAttribute
{
    public MaxWordsAttribute(int wordCount)
        : base("Too many words in {0}")
    {
        WordCount = wordCount;
    }

    public int WordCount { get; set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            var wordCount = value.ToString().Split(' ').Length;
            if (wordCount > WordCount)
            {
                return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            }
        }
        return ValidationResult.Success;
    }
}
[MaxWords(1,ErrorMessage="Only 1 word please")]
        public string ReviewerName { get; set; }

or

public class RestaurantReview : IValidatableObject
{
    public int Id { get; set; }

    [Range(1, 10, ErrorMessage = "1(worst) to 10(best)")]
    [Required] // Not necessary as int is value type
    public int Rating { get; set; }

    [Required]
    [StringLength(1024)]
    public string Body { get; set; }

    [Display(Name = "User Name")]
    [DisplayFormat(NullDisplayText = "anonymous")]

    [MaxWords(1,ErrorMessage="Only 1 word please")]
    public string ReviewerName { get; set; }

    // Not necessary, but makes some scenarios easier
    public int RestaurantId { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Rating < 2 && ReviewerName.ToLower().StartsWith("scott"))
        {
            yield return new ValidationResult("Sorry, Scott, you can't do this!");
        }
    }
}

Great to more complex validations.

image
Notice the validation summary is only showing errors not related to something on the form.. change to false to display all errors.  Yield return is interesting.

| | # 
( MVC4 )
  • Schema First (import schema and generate classes)
  • Model First (graphical designer.. EF generates classes and DB)
  • Code First (EF generates DB)

Enable-Migrations

nvarchar(max) – strings!

var model = _db.Restaurants
                .OrderByDescending(r => r.Reviews.Average(review => review.Rating))
                .Select(r => new RestaurantListViewModel
                        {
                            Id = r.Id,
                            Name = r.Name,
                            City = r.City,
                            Country = r.Country,
                            NumberOfReviews = r.Reviews.Count()
                        });

Using a ViewModel

Searching / Filtering

// null for unit test helping
public ActionResult Index(string searchTerm = null)
{
    var model = _db.Restaurants
                    .OrderByDescending(r => r.Reviews.Average(review => review.Rating))
                    .Where(r => searchTerm == null || r.Name.StartsWith(searchTerm))
                    .Take(10)
                    .Select(r => new RestaurantListViewModel
                            {
                                Id = r.Id,
                                Name = r.Name,
                                City = r.City,
                                Country = r.Country,
                                NumberOfReviews = r.Reviews.Count()
                            });
    return View(model);
}

image
Handy putting in the searchTerm in query string.

<form method="get">
    <input type="search" name="searchTerm" />
    <input type="submit" name="Search by Name" />
</form>

image
Get is perfect for a search request.

| | # 
( MVC4 )

A beginner tutorial, but interesting stuff

Routing

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

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

Controller, Action, Id

public ActionResult Index()
        {
            var controller = RouteData.Values["controller"];
            var action = RouteData.Values["action"];
            var id = RouteData.Values["id"];
            var message = String.Format("{0}::{1} {2}", controller, action, id);

            ViewBag.Message = message;
            return View();
        }

image

// Cuisine/french  (french is more of a parameter)
            routes.MapRoute(
                 "Cuisine", // The name of the route to map
                 "cuisine/{name}", // URL
                 new {controller = "Cuisine", action="Search", name=""}, // Defaults
                 null // Constraints
                );

Adding a new route

public ActionResult Search()
{
    return Content("Hello");
    //return View();
}

Simplest possible thing to do is to send a string back.

image
The result of a Controller method.. all these derive from ActionResult

Return RedirectToAction, Json

Views

@VirtualPath = displays path

@Model.Count()

@ { } is a code block… C# code

| | # 
# Wednesday, 11 December 2013
( MVC4 )

Authorize on screen 3) ie Create

    [Authorize(Roles="Admin")]
    public class EmployeeController : Controller
    {

Admin Role created in the seed method of migrations.

CSRF – Cross Site Request Forgery attack

Compare cookie

image
If @Html.AntiForgeryToken is not available in the view.

Deploy to Azure

image
Have already got 1 free database for another website.

image
Right clicked on Web Project, then publish, new, Azure, and select emanagerwinter.  Check the update database.

Data doesn’t seem to be being seeded

image
2 websites sharing the same database.  dbo schema is the new emanagerwinter site.  Seed data didn’t work.  I registered a user bob and the database connection seems to work.

Go with direct SQL upodates for now!  Exported in SQL2005 so it would run on Azure okay.

USE [eManager]
GO
SET IDENTITY_INSERT [dbo].[Departments] ON

GO
INSERT [dbo].[Departments] ([Id], [Name]) VALUES (1, N'Engineering')
GO
INSERT [dbo].[Departments] ([Id], [Name]) VALUES (2, N'Sales')
GO
INSERT [dbo].[Departments] ([Id], [Name]) VALUES (3, N'Shipping')
GO
INSERT [dbo].[Departments] ([Id], [Name]) VALUES (4, N'Human Resources')
GO
SET IDENTITY_INSERT [dbo].[Departments] OFF
GO
SET IDENTITY_INSERT [dbo].[Employees] ON

GO
INSERT [dbo].[Employees] ([Id], [Name], [Department_Id], [HireDate]) VALUES (1, N'Soctt', 1, CAST(0x0000A16200000000 AS DateTime))
GO
INSERT [dbo].[Employees] ([Id], [Name], [Department_Id], [HireDate]) VALUES (2, N'Bob', 1, CAST(0x0000A29200000000 AS DateTime))
GO
INSERT [dbo].[Employees] ([Id], [Name], [Department_Id], [HireDate]) VALUES (3, N'Scott Allen 2', 1, CAST(0x0000A10100000000 AS DateTime))
GO
INSERT [dbo].[Employees] ([Id], [Name], [Department_Id], [HireDate]) VALUES (4, N'Dave Mateer', 1, CAST(0x0000692400000000 AS DateTime))
GO
INSERT [dbo].[Employees] ([Id], [Name], [Department_Id], [HireDate]) VALUES (5, N'asdf', 1, CAST(0x00009F9800000000 AS DateTime))
GO
INSERT [dbo].[Employees] ([Id], [Name], [Department_Id], [HireDate]) VALUES (7, N'asdf4', 1, CAST(0x00009F9800000000 AS DateTime))
GO
SET IDENTITY_INSERT [dbo].[Employees] OFF
GO
INSERT [dbo].[AspNetRoles] ([Id], [Name]) VALUES (N'0826a435-19f9-4a62-9b15-8bf12104f372', N'Admin')
GO
INSERT [dbo].[AspNetUsers] ([Id], [UserName], [PasswordHash], [SecurityStamp], [Discriminator]) VALUES (N'4a444c76-6ded-4061-af22-66a1350f5ee2', N'dave', N'AAH4pnZsBwzdMH0btLrhlbYhjqpwYWDEQgO7LColDtUW0HwqqGrrO3aQ1Q6K4NNUJg==', N'b61b5738-8150-4124-9b6b-d32f5474e77a', N'ApplicationUser')
GO
INSERT [dbo].[AspNetUserRoles] ([UserId], [RoleId]) VALUES (N'4a444c76-6ded-4061-af22-66a1350f5ee2', N'0826a435-19f9-4a62-9b15-8bf12104f372')
GO

FTP Diagnostics - http://stackoverflow.com/questions/11112272/download-azure-diagnostic-logs and http://weblogs.asp.net/bleroy/archive/2012/06/12/azure-web-sites-ftp-credentials.aspx

drop table dbo.CreateEmployeeViewModels
drop table dbo.__MigrationHistory
drop table dbo.AspNetRoles
drop table dbo.AspNetUserClaims
drop table dbo.AspNetUserLogins
drop table dbo.AspNetUserRoles
drop table dbo.AspNetUsers
drop table dbo.Departments
drop table dbo.Employees

Ahh – it works if I do this:

  • Drop all tables on live
  • Update-database –verbose
  • ****IMPORTANT Press the preview button!
  • Press deploy

Streaming Diagnostic Logs

http://weblogs.asp.net/scottgu/archive/2013/04/30/announcing-the-release-of-windows-azure-sdk-2-0-for-net.aspx

image
Seeing live tracing info from azure inside VS.

public ActionResult Index()
        {
            Trace.TraceInformation(": In Index of home controller");
            var allDepartments = _db.Departments;

            return View(allDepartments);
        }

image
Right click and Open Streaming logs may need to turn on)

| | # 
( MVC4 )

Html Helpers

  • Html.EditorFor (including datetime picker)
  • ValidationMessageFor
  • AntiForgeryToken
  • ValidationSummary
  • LabelFor
  • ActionLink eg Create Employee

RedirectToAction is the C# similar method to ActionLink, which is used after an HttpPost.

ViewModel

Decouple the objects you use to save from objects that user will give you.

// Specific to the Create
public class CreateEmployeeViewModel
{
    [Key]
    public int CreateEmployeeVMId { get; set; }

    // This will put in the form as type=hidden
    [HiddenInput(DisplayValue=false)]
    public int DepartmentId { get; set; }
        
    [Required]
    public string Name { get; set; }
        
    [Required]
    // Only the date
    [DataType(DataType.Date)]
    public DateTime HireDate { get; set; }
}

Creating a ViewModel and decorating it.  Essentially a DTO just for Create/Save.  Security better.

image
1) Home page showing departments

image
2) Goes to /department/detail/x

public class DepartmentController : Controller
{
    private readonly IDepartmentDataSource _db;

    public DepartmentController(IDepartmentDataSource db)
    {
        _db = db;
    }

    public ActionResult Detail(int id)
    {
        var model = _db.Departments.Single(dep => dep.Id == id);
        return View(model);
    }
}

image
3) Employee/Create and CreatePost

public class EmployeeController : Controller
{
    private readonly IDepartmentDataSource _db;

    public EmployeeController(IDepartmentDataSource db)
    {
        _db = db;
    }

    [HttpGet]
    public ActionResult Create(int departmentId)
    {
        var model = new CreateEmployeeViewModel();
        model.DepartmentId = departmentId;

        return View(model);
    }

    [HttpPost]
    public ActionResult Create(CreateEmployeeViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            var department = _db.Departments.Single(dep => dep.Id == viewModel.DepartmentId);
            var employee = new Employee();
            employee.Name = viewModel.Name;
            employee.HireDate = viewModel.HireDate;
            department.Employees.Add(employee);

            _db.Save();

            // Redirect to /department/details
            return RedirectToAction("detail", "department", new { id = viewModel.DepartmentId });
        }

        return View(viewModel);
    }
}

Javascript and server side annotations working.  Notice that DI is happening automatically.

| | # 

Want to display the list of Departments on the home page:

image

// Only want HomeController to know about IDepartmentDataSource and not a concreate implementation
        //private IDepartmentDataSource _db = new ApplicationDbContext();

        // A private field.
        private IDepartmentDataSource _db;

        // Poor mans DI
        public HomeController() : this(new ApplicationDbContext())
        {}

        public HomeController(IDepartmentDataSource db)
        {
            _db = db;
        }

Use IoC – Ninject, Unity, StructureMap

image

StructureMap plugs into the MVC Runtime. so we don’t need poor mans DI.

namespace eManager.Web.DependencyResolution {
    public static class IoC {
        public static IContainer Initialize() {
            ObjectFactory.Initialize(x =>
                        {
                            x.Scan(scan =>
                                    {
                                        scan.TheCallingAssembly();
                                        scan.WithDefaultConventions();
                                    });
                            // When you see something that needs IDep... then the concrete type to use is ApplicationDbContext
                            // and scope to a particular Http request
                            x.For<IDepartmentDataSource>().HttpContextScoped().Use<ApplicationDbContext>();
                        });
            return ObjectFactory.Container;
        }
    }
}

Scaffold

Right click inside the controller..

public ActionResult Index()
        {
            var allDepartments = _db.Departments;

            return View(allDepartments);
        }

image

Then we rewrote the scaffold like this:

@model IEnumerable<eManager.Domain.Department>

@{
    ViewBag.Title = "All Departments";
}

<h1>@ViewBag.Title</h1>

<ul>
    @foreach (var department in Model)
    {
        <li>@department.Name</li>
        @*<a href="/department/detail/5"></a>*@
    }
</ul>
| | # 
( EF6 | MVC4 | MVC5 )

public class Employee
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
}

EF can do change tracking if make virtual. Using alt to add virtual everywhere.

Migrations


namespace eManager.Web.Infrastructure
{
    public class DepartmentDb : DbContext, IDepartmentDataSource
    {
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Department> Departments { get; set; }

        IQueryable<Employee> IDepartmentDataSource.Employees
        {
            get { return Employees; }
        }

        IQueryable<Department> IDepartmentDataSource.Departments
        {
            get { return Departments; }
        }
    }
}

Super simple database abstraction.

image
Hmm –authentication is wired into MVC5 in Models\IdentityModels… but inherits from IdentityDbContext

Stripped out the authentication stuff for now to get compiling.  Commented out AccountController and ApplicationDbContext classes.

image

image
Enabling automatic migrations

Seed allows us to initally populate eg

  • Lookup tables

Update-Database

image

Hmmmm am not getting migration file created nor database created in App_Data

Version2

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDepartmentDataSource
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }

        public DbSet<Employee> Employees { get; set; }
        public DbSet<Department> Departments { get; set; }

        IQueryable<Employee> IDepartmentDataSource.Employees
        {
            get { return Employees; }
        }

        IQueryable<Department> IDepartmentDataSource.Departments
        {
            get { return Departments; }
        }
    }

To make a simple happy path for easy single migrations, I’ve added our entities to the existing context.

internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
    }

    protected override void Seed(ApplicationDbContext context)
    {
        context.Departments.AddOrUpdate(d => d.Name,
                new Department { Name="Engineering"},
                new Department { Name="Sales"},
                new Department { Name="Shipping"},
                new Department { Name="Human Resources"}
                );

        // Could you use this way
        if (!context.Users.Any(u => u.UserName == "dave"))
        {
            var roleStore = new RoleStore<IdentityRole>(context);
            var roleManager = new RoleManager<IdentityRole>(roleStore);

            var userStore = new UserStore<ApplicationUser>(context);
            var userManager = new UserManager<ApplicationUser>(userStore);
            var user = new ApplicationUser { UserName = "dave" };

            userManager.Create(user, "letmein");
            roleManager.Create(new IdentityRole { Name = "admin" });
            userManager.AddToRole(user.Id, "admin");
        }
    }
}

Alt V E O – Package Manager console

update-database –verbose

| | # 
# Wednesday, 06 November 2013
( MVC4 )

http://pluralsight.com/training/Courses/TableOfContents/mvc4-building

image
Standard MVC4 template is HTML5.  And the viewport means that mobile apps will not start zoomed about, and the page will adapt (reactive)

Modernizr helps with older browsers

ViewBag is a dynamically typed property

image

Statically typed

image
Careful of lower case model (a directive), and upper case Model in razor.

Testing

image
Changed out of the box test to assert that the strongly typed Model is not null.

Javascript and CSS

image
Patching in CSS

Routes

image
Routing engine working.  There is a better way.

| | #