Quantcast
Channel: damienbod – Software Engineering
Viewing all 354 articles
Browse latest View live

Visualizing Elasticsearch Watcher events using ASP.NET 5, SignalR and Angular

$
0
0

This article shows how to display Elasticsearch Watcher events in an Angular UI using SignalR. The Elasticsearch Watcher events are sent to a MVC 6 controller in an ASP.NET 5 application. The action method in the controller handles the watcher events and sends the data to the Angular UI using SignalR and the SignalR client library angular-signalr-hub.

Code: https://github.com/damienbod/AspNet5Watcher

2015.09.20: Updated to ASP.NET beta 7
2015.10.20: Updated to ASP.NET beta 8

Setting up SignalR in a ASP.NET 5 MVC 6 application

To use SignalR in an ASP.NET 5 project, it must be added to the project in the project.json file in the dependencies. The following configuration is required:

"Microsoft.AspNet.SignalR.Server": "3.0.0-*"

Here’s the configuration for the ASP.NET 5 project using the beta5 version.

    "dependencies": {
        "Microsoft.AspNet.Diagnostics": "1.0.0-beta8",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8",
        "Microsoft.AspNet.Mvc": "6.0.0-beta8",
        "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8",
        "Microsoft.AspNet.StaticFiles": "1.0.0-beta8",
        "Microsoft.Framework.Logging": "1.0.0-beta8",
        "Microsoft.Framework.Logging.Console": "1.0.0-beta8",
        "Microsoft.Framework.Logging.Debug": "1.0.0-beta8",
        "NEST": "1.7.0",
        "Microsoft.Net.Http": "2.2.29",
        "Microsoft.AspNet.SignalR.Server": "3.0.0-beta8-15656",
        "NEST.Watcher": "1.0.0-beta2"
    },

Now SignalR can be added to the Startup.cs class. This just requires the AddSignalR and the UseSignalR methods in the ConfigureServices and Configure methods respectively.

public IConfigurationRoot Configuration { get; set; }

public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
             var builder = new ConfigurationBuilder()
                .SetBasePath(appEnv.ApplicationBasePath)
                .AddJsonFile("config.json");
            Configuration = builder.Build();
}

public void ConfigureServices(IServiceCollection services)
{
            services.Configure<ApplicationConfiguration>(Configuration.GetSection("ApplicationConfiguration"));

            services.AddMvc();
            services.AddSignalR(options =>
            {
                options.Hubs.EnableDetailedErrors = true;
            });

            services.AddScoped<SearchRepository, SearchRepository>();
            services.AddInstance(_configuration);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.MinimumLevel = LogLevel.Information;
	loggerFactory.AddConsole();
	loggerFactory.AddDebug();

	app.UseIISPlatformHandler();

	app.UseExceptionHandler("/Home/Error");

	app.UseStaticFiles();
	app.UseMvc(routes =>
	{
		routes.MapRoute(
			name: "default",
			template: "{controller=Home}/{action=Index}/{id?}");
	});
	app.UseSignalR();

Now that SignalR is up and running on the server, a Hub can be created and used. The Hub in this demo is a simple class which inherits from the Hub class.

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace AspNet5Watcher.Hubs
{
    [HubName("alarms")]
    public class AlarmsHub  : Hub
    {
    }

}

Using the SignalR Hub in a MVC 6 Controller

In the MVC 6 controller, WatcherEventsController which is used to handle the Elasticsearch Watcher events, the AlarmsHub is used to send the events to all the SignalR clients. The Hub is added to the controller using the FromServices attribute.

private IConnectionManager _connectionManager;
private IHubContext _alarmsHub;

[FromServices]
public IConnectionManager ConnectionManager
{
	get
	{
		return _connectionManager;
	}
	set
	{
		_connectionManager = value;
		_alarmsHub = _connectionManager.GetHubContext<AlarmsHub>();
	}
}

Then the client can be used in the WatcherEvents/CriticalAlarm method. When Elasticsearch sends new data, the _alarmsHub is used to send the total count of critical data to the SignalR clients and also the last ten critical alarms.

//POST http://localhost:5000/api/WatcherEvents/CriticalAlarm HTTP/1.1
[HttpPost]
[Route("CriticalAlarm")]
public IActionResult Post([FromBody]int countNewCriticalAlarms)
{  
	if (countNewCriticalAlarms != _criticalAlarmsCount )
	{
		var newCriticalAlarmsCount = countNewCriticalAlarms - _criticalAlarmsCount;
		_criticalAlarmsCount = countNewCriticalAlarms;

		_alarmsHub.Clients.All.sendtotalalarmscount(countNewCriticalAlarms);

		_alarmsHub.Clients.All.sendlasttencriticalalarms(
                       _searchRepository.SearchForLastTenCriticalAlarms());
	}

	return new HttpStatusCodeResult(200);
}

Using SignalR with a Angular JS client

The SignalR events are implemented in Angular using angular-signalr-hub. This is not necessary but I like its simplicity when using it.

The SignalR packages are added using bower. These are added to the bower.json file in the dependencies property. This will download and install the default bower settings. If you required only the min files, then you need to customize the exportsOverride for the packages.

"angular-signalr-hub": "1.5.0",
"signalr": "2.2.0"

Then the javascript files are added to the index.html file before the app.js file.

<script src="lib/jquery/js/jquery.js"></script>
<script src="lib/signalr/jquery.signalr.js"></script>
<script src="lib/angular-signalr-hub/signalr-hub.js"></script>

<script type="text/javascript" src="app.js"></script>

Now the module can be added to your main angular module and also the new alarmsDisplay route can be configured in the app.js file.

(function () {
    var mainApp = angular.module("mainApp", ["ui.router", "SignalR"]);

	mainApp.config(["$stateProvider", "$urlRouterProvider",
		function ($stateProvider, $urlRouterProvider) {
		    $urlRouterProvider.otherwise("/home/createAlarm");

            	$stateProvider
                    .state("home", { abstract: true, url: "/home", templateUrl: "/templates/home.html" })
                        .state("createAlarm", {
                            parent: "home", url: "/createAlarm", templateUrl: "/templates/createAlarm.html", controller: "alarmsController",
                        	
                        })
                    .state("alarms", {
                        url: "/alarms", templateUrl: "/templates/alarms.html", controller: "alarmsDisplayController",

                    })
        }
	]
    );
})();

The application uses an angular controller to add the SignalR client to the UI. This is implemented in the AlarmsDisplayController controller. The SignalR client implements two listeners for the SignalR methods. When the listeners receive an event, the data is added to the root scope of the application.

(function () {
	'use strict';

	var module = angular.module("mainApp");

	var AlarmsDisplayController = (function () {
	    function AlarmsDisplayController($scope, log, alarmsService, $rootScope, Hub, $timeout) {
	        $scope.Vm = this;

	        this.$scope = $scope;
	        this.alarmsService = alarmsService;
	        this.log = log;
	        this.$rootScope = $rootScope;
	        this.Hub = Hub;
	        this.$timeout = $timeout;

	        this.log.info("AlarmsDisplayController called");
	        this.Name = "Displaying Watcher alarms";

	        var hub = new Hub('alarms', {

	            //client side methods
	            listeners: {
	                'sendtotalalarmscount': function (count) {
	                    $rootScope.AlarmsCount = count;
	                    $rootScope.$apply();
	                    console.log("Recieved SendTotalAlarmsCount:" + count);
	                },
	                'sendlasttencriticalalarms': function (lasttenalarms) {
	                    $rootScope.LastTenCriticalAlarms = lasttenalarms;
	                    $rootScope.$apply();
	                    console.log(lasttenalarms);
	                },
	            },

	            //handle connection error
	            errorHandler: function (error) {
	                console.error(error);
	            },

	            stateChanged: function (state) {
	                switch (state.newState) {
	                    case $.signalR.connectionState.connecting:
	                        console.log("signalR.connectionState.connecting" + state.newState)
	                        break;
	                    case $.signalR.connectionState.connected:
	                        console.log("signalR.connectionState.connected" + state.newState)
	                        break;
	                    case $.signalR.connectionState.reconnecting:
	                        console.log("signalR.connectionState.reconnecting" + state.newState)
	                        break;
	                    case $.signalR.connectionState.disconnected:
	                        console.log("signalR.connectionState.disconnected" + state.newState)
	                        break;
	                }
	            }
	        });
	    }

	    return AlarmsDisplayController;
	})();

    // this code can be used with uglify
	module.controller("alarmsDisplayController",
		[
			"$scope",
			"$log",
			"alarmsService",
            '$rootScope',
            'Hub',
            '$timeout',
			AlarmsDisplayController
		]
	);
})();

Once the controller is completed, the data can be used in the alarms display HTML file in the templates folder in the wwwroot. This HTML will be updated every time a SignalR event is sent to all the listening clients.

<div class="container">
    <div class="row">
        <div class="col-sm-9">
            <h4>{{Vm.Name}}, Total critical alarms:</h4>
        </div>
        <div class="col-sm-3">
            <div class="alarmsDisplay round-corners-5px">
                <p>{{$root.AlarmsCount}}</p>
            </div>
        </div>
    </div>
</div>

<div class="container">
    <h4>Last ten critical alarms</h4>
    <table class="table">
        <thead>
            <tr>
                <th>Message</th>
                <th>Created</th>
                <th>Id</th>
            </tr>
        </thead>
        <tbody>
            <tr data-ng-repeat="alarm in $root.LastTenCriticalAlarms">
                <td>{{alarm.Message}}</td>
                <td>{{alarm.Created}}</td>
                <td>{{alarm.Id}}</td>
            </tr>
        </tbody>
    </table>
</div>


Displaying the Elasticsearch Watcher Data

Every time a new critical alarm is added or created, the alarm is saved to Elasticsearch and within 10s, Elasticsearch Watcher notices this and calls the MVC 6 action method. The action method then uses SignalR and the UI is updated in real time.

SignalRWatcher_01

The HTML page is updated is real time without a refresh using SignalR.

SignalRWatcher_02

Conclusion

Using Elasticsearch Watcher together with SignalR and ASP.NET 5, some really cool features can be implemented which could be very useful when solving a business problem for a particular use case.

Note

At present the application uses HttpClient directly with Elasticsearch to create and delete the Elasticsearch Watcher, with a not so nice magic string. This will be updated as soon as the next NEST Elasticsearch Watcher is released and supports the watcher features used in this application.

Links:

https://github.com/JustMaier/angular-signalr-hub

https://github.com/aspnet/musicstore

https://github.com/SignalR/SignalR

https://www.elastic.co/guide/en/watcher/current/index.html

http://amsterdam.luminis.eu/2015/06/23/first-steps-with-elastic-watcher/

https://github.com/elastic/elasticsearch-watcher-net

https://www.elastic.co/guide/en/watcher/current/actions.html#actions-index



ASP.NET 5 with SQLite and Entity Framework 7

$
0
0

This article shows how to use SQLite with ASP.NET 5 using Entity Framework 7. EF7 can now create SQLite databases using Entity Framework migrations which was not possible in previous versions.

Code: https://github.com/damienbod/AspNet5SQLite

16.10.2015: Updated to ASP.NET 5 beta8

The project was created using Omnisharp generator-aspnet and then upgraded to version beta6 using the command line tools dnu and dnvm. The dnvm was used to set the default runtime. The default runtime can be checked using dnvm list.

aspnet5sqlite_updatebeta8_01

The global.json in the solution also needs to be updated to match the default runtime version. Now the application can be started from the command line or Visual Studio. If required, the specific runtime can also be defined in the project file.

{
    "projects": [ "src", "test" ],
    "sdk": {
        "version": "1.0.0-beta8",
        "runtime": "clr",
        "architecture": "x86"
    }
}

The required dependencies are added to the project.json file. This is the hardest part, as you have to figure out which packages are required and also if the packages work together.

"dependencies": {
        "EntityFramework.Commands": "7.0.0-beta8",
        "EntityFramework.SQLite": "7.0.0-beta8",
        "Microsoft.AspNet.Diagnostics": "1.0.0-beta8",
        "Microsoft.AspNet.Diagnostics.Entity": "7.0.0-beta8",
        "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-beta8",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8",
        "Microsoft.AspNet.Mvc": "6.0.0-beta8",
        "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8",
        "Microsoft.AspNet.StaticFiles": "1.0.0-beta8",
        "Microsoft.AspNet.Tooling.Razor": "1.0.0-beta8",
        "Microsoft.Framework.Configuration.Abstractions": "1.0.0-beta8",
        "Microsoft.Framework.Configuration.Json": "1.0.0-beta8",
        "Microsoft.Framework.Configuration.UserSecrets": "1.0.0-beta8",
        "Microsoft.Framework.Logging": "1.0.0-beta8",
        "Microsoft.Framework.Logging.Console": "1.0.0-beta8",
        "Microsoft.Framework.Logging.Debug": "1.0.0-beta8"
    },

Now the EF command can be added to the same project.json file.

  "commands": {
        "web": "Microsoft.AspNet.Server.Kestrel",
        "ef": "EntityFramework.Commands"
    },

Next step is to create the POCO classes and the Entity Framework context. This is a simple class with a Id of type long.

public class DataEventRecord
{
	public long Id { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }
	public DateTime Timestamp { get; set; }
}

The context is defined with a single DbSet and a key mapping.

namespace AspNet5SQLite.Model
{
    using Microsoft.Data.Entity;

    // >dnx . ef migration add testMigration
    public class DataEventRecordContext : DbContext
    {
        public DbSet<DataEventRecord> DataEventRecords { get; set; }
      
        protected override void OnModelCreating(ModelBuilder builder)
        { 
            builder.Entity<DataEventRecord>().HasKey(m => m.Id); 
            base.OnModelCreating(builder); 
        } 
    }
}

Entity Framework is then added in the Startup class. The SQLite Provider is used and the connection string defines the path to the SQLite file.

public void ConfigureServices(IServiceCollection services)
{
	var connection = Configuration["Production:SqliteConnectionString"];

	services.AddEntityFramework()
		.AddSqlite()
		.AddDbContext<DataEventRecordContext>(options => options.UseSqlite(connection));

	services.AddMvc();
	services.AddScoped<IDataEventRecordResporitory, DataEventRecordResporitory>();
}

The connection string is defined in the config.json file in this application. It is important to define the path as shown below ‘C:\\…’, otherwise the application will not work.

{
    "Production": {
        "SqliteConnectionString": "Data Source=C:\\git\\damienbod\\AspNet5SQLite\\src\\AspNet5SQLite\\dataeventrecords.sqlite"
    }
}

Now the database can be created using EF migrations. Open the command line and execute the following command in the src directory of the application.

dnx  ef migration add testMigration

Now create the database:

dnx ef database update

This will create a new Migrations folder with the generated classes. Run the application and the database will be created. This can be checked using SQLite Manager in Firefox:

SQLite_ASP_NET5_02

The context can now be used. A simple repository has been created for the CRUD operations.

namespace AspNet5SQLite.Repositories
{
    using System.Collections.Generic;
    using System.Linq;

    using AspNet5SQLite.Model;

    using Microsoft.AspNet.Mvc;
    using Microsoft.Framework.Logging;

    public class DataEventRecordRepository : IDataEventRecordRepository
    {
        private readonly DataEventRecordContext _context;

        private readonly ILogger _logger;

        public DataEventRecordRepository(DataEventRecordContext context, ILoggerFactory loggerFactory)
        {
            _context = context;
            _logger = loggerFactory.CreateLogger("IDataEventRecordRepository");          
        }

        public List<DataEventRecord> GetAll()
        {
            _logger.LogCritical("Getting all the existing records");
            return _context.DataEventRecords.ToList();
        }

        public DataEventRecord Get(long id)
        {
            return _context.DataEventRecords.First(t => t.Id == id);
        }

        [HttpPost]
        public void Post(DataEventRecord dataEventRecord )
        {
            _context.DataEventRecords.Add(dataEventRecord);
            _context.SaveChanges();
        }

        public void Put(longid, [FromBody]DataEventRecord dataEventRecord)
        {
            _context.DataEventRecords.Update(dataEventRecord);
            _context.SaveChanges();
        }

        public void Delete(long id)
        {
            var entity = _context.DataEventRecords.First(t => t.Id == id);
            _context.DataEventRecords.Remove(entity);
            _context.SaveChanges();
        }
    }
}

The repository is used in the MVC controller:

namespace AspNet5SQLite.Controllers
{
    using System.Collections.Generic;

    using AspNet5SQLite.Model;
    using AspNet5SQLite.Repositories;

    using Microsoft.AspNet.Mvc;

    [Route("api/[controller]")]
    public class DataEventRecordsController : Controller
    {
        private readonly IDataEventRecordRepository _dataEventRecordRepository;

        public DataEventRecordsController(IDataEventRecordRepository dataEventRecordRepository)
        {
            _dataEventRecordRepository = dataEventRecordRepository;
        }

        [HttpGet]
        public IEnumerable<DataEventRecord> Get()
        {
            return _dataEventRecordRepository.GetAll();
        }

        [HttpGet("{id}")]
        public DataEventRecord Get(long id)
        {
            return _dataEventRecordRepository.Get(id);
        }

        [HttpPost]
        public void Post([FromBody]DataEventRecord value)
        {
            _dataEventRecordRepository.Post(value);
        }

        [HttpPut("{id}")]
        public void Put(long id, [FromBody]DataEventRecord value)
        {
            _dataEventRecordRepository.Put(id, value);
        }

        [HttpDelete("{id}")]
        public void Delete(long id)
        {
            _dataEventRecordRepository.Delete(id);
        }
    }
}

The default IoC from ASP.NET 5 is used to define the dependencies in the startup class.

services.AddScoped<IDataEventRecordRepository, DataEventRecordRepository>();

The server side of the application is up and running and CRUD operations function with an MVC 6 application using Entity Framework code first with SQLite.

A simple angular client application has been created to test the CRUD operations.

Once the application is started, this can be tested using the following links:

http://localhost:30660/index.html
http://localhost:30660/#/home/overview/details/1
http://localhost:30660/index.html#/home/overview/create

SQLite_ASP_NET5_04

You can also verify the results in the SQLite Manager in Firefox:

SQLite_ASP_NET5_03

Conclusion

It is now much easy to set up a SQLite database in Entity Framework compared to previous versions, and once the initial bugs from the migrations are sorted, this will a great solution for certain problems, application types.

Links:

https://github.com/Schr3da/ASP.net-vnext-samples

http://bitoftech.net/2014/11/18/getting-started-asp-net-5-mvc-6-web-api-entity-framework-7/

https://github.com/OmniSharp/generator-aspnet


ASP.NET 5 Action Filters

$
0
0

This article shows how the ActionFilterAttribute class can be used in an ASP.NET 5 MVC application.

The ActionFilterAttribute class is an implementation of the IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, and the IOrderedFilter interfaces. This filter can be used as a method filter, controller filter, or global filter for all MVC HTTP requests, or more precisely an ActionFilterAttribute can be used directly, as a ServiceFilter, as a TypeFilter, or in the Startup class. The ServiceFilter class can be used to apply the different custom attribute implementations in the controller classes. By using the ServiceFilter, it is possible to use constructor injection using the application IoC. This is great improvement compared to the the previous version which forced us the use property injection for child dependencies or to add the dependencies directly.

Code: https://github.com/damienbod/AspNet5Filters

16.10.2015: Updated to ASP.NET 5 beta8

Using the filter as a ServiceFilter

In the following examples, custom implementations of the ActionFilterAttribute are used which implement the four synchronous method which can be overridden. The ILoggerFactory is used to log the method calls which is added to the custom action filter using constructor injection.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Framework.Logging;

namespace AspNet5.Filters
{
    public class ConsoleLogActionOneFilter : ActionFilterAttribute
    {
        private readonly ILogger _logger;

        public ConsoleLogActionOneFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("ConsoleLogActionOneFilter");
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation("OnActionExecuting");
            base.OnActionExecuting(context);
        }

        public override void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation("OnActionExecuted");
            base.OnActionExecuted(context);
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            _logger.LogInformation("OnResultExecuting");
            base.OnResultExecuting(context);
        }

        public override void OnResultExecuted(ResultExecutedContext context)
        {
            _logger.LogInformation("OnResultExecuted");
            base.OnResultExecuted(context);
        }
    }
}

Because the filters will be used as a ServiceType, the different custom filters need to be registered with the framework IoC. If the action filters were used directly, this would not be required.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddScoped<ConsoleLogActionOneFilter>();
    services.AddScoped<ConsoleLogActionTwoFilter>();
    services.AddScoped<ClassConsoleLogActionOneFilter>();
}

The different custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute.

using System;
using System.Collections.Generic;
using AspNet5.Filters.ActionFilters;
using AspNet5.Filters.ExceptionFilters;
using AspNet5.Filters.ResourceFilters;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.Logging;

namespace AspNet5.Controllers
{
    [ServiceFilter(typeof(ClassConsoleLogActionOneFilter))]
    [Route("api/[controller]")]
    public class TestController : Controller
    {
        private readonly ILogger _logger;

        public TestController(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("TestController");
        }

        // GET: api/test
        [HttpGet]
        [ServiceFilter(typeof(ConsoleLogActionOneFilter))]
        [ServiceFilter(typeof(ConsoleLogActionTwoFilter))]
        public IEnumerable<string> Get()
        {
            _logger.LogInformation("Executing Http Get all");
            return new string[] { "test data one", "test data two" };
        }

    }
}

When the application is started (dnx web), the HTTP request runs as following:

aspnet5actionfilters_004_test

The action overrides are executed first, then the result overrides. The class filter wraps the method filters.

Using the filter as a global filter

The custom implementations of the action filter can also be added globally in the ConfigureServices method in the startup class. The is not added using the framework IoC here, so the loggerFactory is created and added manually in the AddMVC method via the config options.

public void ConfigureServices(IServiceCollection services)
{
	var loggerFactory = new LoggerFactory { MinimumLevel = LogLevel.Debug };
	loggerFactory.AddConsole();
	loggerFactory.AddDebug();

	services.AddMvc(
		config =>
			{
				config.Filters.Add(new GlobalFilter(loggerFactory));
			});

	services.AddScoped<ConsoleLogActionOneFilter>();
	services.AddScoped<ConsoleLogActionTwoFilter>();
	services.AddScoped<ClassConsoleLogActionBaseFilter>();
	services.AddScoped<ClassConsoleLogActionOneFilter>();

}

The global filter is wrapped outside of the controller class filters per default.

aspnet5actionfilters_001_global

Using the filter with base controllers

The action filter can also be applied to child and parent MVC controllers. The action filter on the child controller is wrapped around the base controller.

[ServiceFilter(typeof(ClassConsoleLogActionOneFilter))]
[Route("api/[controller]")]
public class TestWithBaseController : BaseController
{
  private readonly ILogger _logger;

  public TestWithBaseController(ILoggerFactory loggerFactory): base(loggerFactory)
  {
		_logger = loggerFactory.CreateLogger("TestWithBaseController");
  }

The base controller implementation:

[ServiceFilter(typeof(ClassConsoleLogActionBaseFilter))]
[Route("api/[controller]")]
public class BaseController : Controller
{
  private readonly ILogger _logger;

  public BaseController(ILoggerFactory loggerFactory)
  {
      _logger = loggerFactory.CreateLogger("BaseController");
  }

  [HttpGet]
  [HttpGet("getall")]
  [ServiceFilter(typeof(ConsoleLogActionOneFilter))]
  [ServiceFilter(typeof(ConsoleLogActionTwoFilter))]
  public IEnumerable<string> GetAll()
  {
		_logger.LogInformation("Executing Http Get all");
		return new string[] { "test data one", "test data two" };
  }
}

A HTTP request is executed as follows:

aspnet5actionfilters_002_base

Using the filter with an order

The execution order in the HTTP request can also be set when using an action filter which is a great improvement compared to Web API. The action filters with the highest order value will be executed last. It doesn’t matter if the filter is defined on a class or on a method, if the order properties are different, this property will be used. By using the order property on a filter, you have total control. This is great to have, but I would avoid using this if possible and stick to the conventions. I would try to design the application so that the usage of the order is not required.

[ServiceFilter(typeof(ClassConsoleLogActionOneFilter), Order=3)]
[Route("api/[controller]")]
public class TestWithOrderedFiltersController : Controller
{
	private readonly ILogger _logger;

	public TestWithOrderedFiltersController(ILoggerFactory loggerFactory)
	{
		_logger = loggerFactory.CreateLogger("TestWithOrderedFiltersController");
	}

	// GET: api/test
	[HttpGet]
	[ServiceFilter(typeof(ConsoleLogActionOneFilter), Order = 5)]
	[ServiceFilter(typeof(ConsoleLogActionTwoFilter), Order = 2)]
	public IEnumerable<string> Get()
	{
		_logger.LogInformation("TestWithOrderedFiltersController Http Get all");
		return new string[] { "test data one", "test data two" };
	}
}

The execution of the HTTP request is controller by setting the order of the action filters.

aspnet5actionfilters_003_order

Notes

You could also use the ActionFilter directly or as a TypeFilter. See Filip WOJCIESZYN’s blog for a detailed description of this.

Links:

http://www.strathweb.com/2015/06/action-filters-service-filters-type-filters-asp-net-5-mvc-6/

https://github.com/aspnet/Mvc/blob/229724c4eab3bf4fc8390deca9af7e451e5caee7/src/Microsoft.AspNet.Mvc.Core/Filters/ActionFilterAttribute.cs

https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNet.Mvc.Abstractions/Filters


Adding Cache safe links to a Grunt build in ASP.NET 5

$
0
0

The post shows how to add cache safe links and scripts to a HTML file using grunt in a ASP.NET 5 application. The post uses the grunt build configuration from this blog, ( ASP.NET 5 Typescript AngularJS application with a grunt production configuration ).

Code: https://github.com/damienbod/AspNet5TypescriptProductionGrunt

2015.10.20: Updated to ASP.NET beta 8

Strategy

When updating the production deployment, or continuous builds, in a web application, the old versions of the scripts and the CSS files will still be used, because these are saved in the client’s browser cache and the new ones have the same URLs. To solve this, a version hash is used to force the client to load the new files. The following configuration changes the links for every build and every CSS, script file used by the client. If you have a web application with lots of users, this is not a good strategy as you only want the browser to download the new changed files and not all the files. For example, the vendor CSS, script files will not change in every version.

It might also be helpful that the build version number is used and not a random hash for production releases. This would help with issue tracking.

Setup

In the application package.json file, the npm grunt-cache-control package is added. Visual Studio 2015 automatically downloads this for you.

{
    "name": "package",
    "version": "1.0.0",
    "private": true,
    "devDependencies": {
        "grunt": "0.4.5",
        "grunt-bower-install": "^1.6.0",
        "grunt-bower-task": "0.4.0",
        "grunt-contrib-cssmin": "^0.12.2",
        "grunt-contrib-uglify": "0.9.1",
        "grunt-contrib-watch": "0.6.1",
        "grunt-contrib-concat": "0.5.1",
        "uglify-js": "2.4.20",
        "grunt-ts": "4.0.1",
        "grunt-cache-control": "0.2.5"
    }
}

This grunt-cache-control npm package can then be used in the grunt file.

grunt.loadNpmTasks("grunt-cache-control");

A random hash string is created using the code from rob lauren’s blog. Thanks for this. This hash is then added to the version property. This creates a new random number for every build. This ensures that new links and scripts are always used in the browser with each build and not just with each version. This helps with continuous builds.

  grunt.initConfig({
        hash: '<%= ((new Date()).valueOf().toString()) + (Math.floor((Math.random()*1000000)+1).toString()) %>',          
        cache_control: {
            your_target: {
                source: ['wwwroot/index.html'],
                options: {
                    version: "<%= hash %>",
                    links: true,
                    scripts: true,
                    replace: true
                }
            }
        }

   });

The cache_control task is then added to the grunt registerTask which groups the different tasks together for the build.

grunt.registerTask('development', 
      [ 'ts', 
        'concat', 
        'uglify', 
        'cssmin', 
        'cache_control', 
        'watch'
      ]
);

grunt.registerTask('buildserver', 
     [ 'ts', 
       'concat', 
       'uglify', 
       'cssmin', 
       'cache_control'
     ]
);

The grunt task now creates new links with every grunt build.

gruntCache_01

The new links are added in the index.html file.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Demo typescript ASP.NET5</title>
        <link href="lib/mycode.min.css?v=1442604838788656950" rel="stylesheet" />
        <link href="lib/vendor.min.css?v=1442604838788656950" rel="stylesheet" />
    </head>
    <body>
    
        <div ng-app="myapp">
            <div ui-view></div>
        </div>

        <script type="text/javascript" src="lib/vendor.min.js?v=1442604838788656950"></script>
        <script type="text/javascript" src="lib/mycode.min.js?v=1442604838788656950"></script>
    </body>
</html>

Note

This is also a good solution if using Visual Studio 2013, because the required files have to be added to the cs project file. If new js and css are created each time, the csproj file would also need to be changed. This is not required with this configuration solving the client browser cache problem.

Links:

https://www.npmjs.com/package/grunt-cache-control

https://damienbod.wordpress.com/2015/05/12/asp-net-5-typescript-angularjs-application-with-a-grunt-production-configuration/

http://robandlauren.com/2013/08/14/busting-cache-with-grunt/


ASP.NET 5 examples and links

$
0
0

This is my landing page which I use for presentations. See the links underneath for getting started with ASP.NET 5.

Using DataAnnotations and Localization in ASP.NET 5 MVC 6
ASP.NET beta 8
code | blog

ASP.NET 5 MVC 6 Localization
ASP.NET beta 8
code | blog

ASP.NET 5 multiple configurations without using environment variables
ASP.NET beta 8
code | blog

ASP.NET 5 Exception Filters and Resource Filters
ASP.NET beta 8
code | blog

Adding Cache safe links to a Grunt build in ASP.NET 5
ASP.NET beta 8
code | blog

ASP.NET 5 Action Filters
ASP.NET beta 8
code | blog

ASP.NET 5 with SQLite and Entity Framework 7
ASP.NET beta 8
code | blog

An ASP.NET 5 AngularJS application with Elasticsearch, NEST and Watcher
ASP.NET beta 8
code | blog

Using Elasticsearch Watcher to create data events in ASP.NET MVC 6
ASP.NET beta 8
code | blog

Visualizing Elasticsearch Watcher events using ASP.NET 5, SignalR and AngularJS
ASP.NET beta 8
code | blog

Creating HTML themes in ASP.NET 5 using Sass
ASP.NET beta 8
code | blog

ASP.NET 5 MVC 6 Custom Protobuf Formatters
ASP.NET beta 8
code | blog

Drag and drop bootstrap tabs in ASP.NET 5 with AngularJS
ASP.NET beta 8
code | blog

ASP.NET 5 Typescript AngularJS application with a Grunt production configuration
ASP.NET beta 8
code | blog

Batching in an ASP.NET 5 AngularJS application using angular-http-batcher
ASP.NET beta 8
NOTE:
Batching is not supported server side in ASP.NET 5, use Web API Batching.
code | blog

ASP.NET 5 AngularJS application using angular-ui-router
ASP.NET beta 8
code | blog

ASP.NET 5 Links:

Getting Started with ASP.NET 5 and DNX

Installing ASP.NET 5 On Mac OS X

http://www.asp.net/vnext

Microsoft ASP.NET and Web Tools 2015 (Beta8) – Visual Studio 2015

https://github.com/aspnet

ASP.NET 5 Documentation

ASP.NET 5 Schedule and Roadmap

.NET Web Development and Tools Blog

Visual Studio

Visual Studio Code

Yeoman

Building Projects with Yeoman

Introduction to ASP.NET 5, microsoft virtual academy

https://live.asp.net/

http://www.iis.net/downloads/microsoft/httpplatformhandler

https://azure.microsoft.com/en-us/blog/announcing-the-release-of-the-httpplatformhandler-module-for-iis-8/

Coming soon…

go.asp.net

get.asp.net


ASP.NET 5 Exception Filters and Resource Filters

$
0
0

This article shows how an exception filter and a resource filter can be used in ASP.NET 5.

Code: https://github.com/damienbod/AspNet5Filters

16.10.2015: Updated to ASP.NET 5 beta8

An exception filter can be implemented using the IExceptionFilter interface or by implementing the abstract class ExceptionFilter. The ExceptionFilter class implements the IAsyncExceptionFilter, IExceptionFilter and the IOrderedFilter interfaces. In a MVC 6 controller, the ExceptionFilter can be used directly, as a ServiceFilter or as a TypeFilter, on the class itself or applied to single methods. The ExceptionFilter can also be added globally in the Startup class.

A custom resource filter is implemented by using the IResourceFilter interface.
The resource filter method OnResourceExecuting is called before all action filters and exception filters and the resource filter OnResourceExecuted method is called after all action filter and exception filters.

Custom Exception Filter

The following code is an example of a custom filter implemented using the abstract class ExceptionFilterAttribute. The filter does not require a default constructor and can be used to add dependencies which will be added using the MVC 6 IoC.

using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Framework.Logging;

namespace AspNet5.Filters.ExceptionFilters
{
    public class CustomOneLoggingExceptionFilter : ExceptionFilterAttribute
    {
        private readonly ILogger _logger;

        public CustomOneLoggingExceptionFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("CustomOneLoggingExceptionFilter");
        }

        public override void OnException(ExceptionContext context)
        {
            _logger.LogInformation("OnActionExecuting");
            base.OnException(context);
        }

        //public override Task OnExceptionAsync(ExceptionContext context)
        //{
        //    _logger.LogInformation("OnActionExecuting async");
        //    return base.OnExceptionAsync(context);
        //}
    }
}

Custom Resource Filter

A custom filter can also be implemented using the IResourceFilter interface.

using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Framework.Logging;

namespace AspNet5.Filters.ResourceFilters
{
    public class CustomOneResourceFilter : IResourceFilter
    {

        private readonly ILogger _logger;

        public CustomOneResourceFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("CustomOneResourceFilter");
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            _logger.LogInformation("OnResourceExecuted");
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            _logger.LogInformation("OnResourceExecuting");
        }
    }
}

Startup class code configuration

Global filters can be added using the ConfigureServices method in the Startup class. If the custom filters are used as ServiceType filters in the controller classes, the custom filters need to be added to the MVC 6 IoC.

public void ConfigureServices(IServiceCollection services)
{
	var loggerFactory = new LoggerFactory { MinimumLevel = LogLevel.Debug };
	loggerFactory.AddConsole();
	loggerFactory.AddDebug();

	services.AddMvc(
		config =>
			{
				config.Filters.Add(new GlobalFilter(loggerFactory));
				config.Filters.Add(new GlobalLoggingExceptionFilter(loggerFactory));
			});

	services.AddScoped<ConsoleLogActionOneFilter>();
	services.AddScoped<ConsoleLogActionTwoFilter>();
	services.AddScoped<ClassConsoleLogActionBaseFilter>();
	services.AddScoped<ClassConsoleLogActionOneFilter>();

	services.AddScoped<CustomOneLoggingExceptionFilter>();
	services.AddScoped<CustomTwoLoggingExceptionFilter>();
	services.AddScoped<CustomOneResourceFilter>();   
}

Request with Exception with default filters

The TestExceptionController implements the default exception example.

[Route("api/[controller]")]
[ServiceFilter(typeof(CustomTwoLoggingExceptionFilter))]
public class TestExceptionController : Controller
{
	private readonly ILogger _logger;

	public TestExceptionController(ILoggerFactory loggerFactory)
	{
		_logger = loggerFactory.CreateLogger("TestExceptionController");
	}

	[HttpGet]
	[ServiceFilter(typeof(CustomOneLoggingExceptionFilter))]
	[ServiceFilter(typeof(CustomOneResourceFilter))]
	public IEnumerable<string> Get()
	{
		_logger.LogInformation("Executing Http Get before exception");
		throw new Exception("Yes a great exception");
	}

When the HTTP Request is executed in the MVC 6 pipeline, the Resource filters OnResourceExecuting is executed first. Then all the action filter OnResourceExecuting are executed. The method itself is then executed. After this, all the action filter OnResourceExecuted are executed. Then the Exception filters are called. (OnException ). The Resource filter OnResourceExecuted is called at the end.

aspnet5_filters_01[1]

The result can also be viewed in in the console (start the application in the console with dnx web):

aspnet5ExceptionFilters_01

The exception filter closest the the action method is executed first, then the controller class filter, and then the global exception filter. The resource filter is executed at the end.

Request with Exception with handled exception

This example stops executing ExceptionFilters if a CustomException is throw. This is done by setting the Exception property of the ExceptionContext to null. The following OnException methods exception filters are not executed.

public override void OnException(ExceptionContext context)
{
	_logger.LogInformation("OnActionExecuting");
	handleCustomException(context);
	base.OnException(context);
}

private void handleCustomException(ExceptionContext context)
{
	if (context.Exception.GetType() == typeof(CustomException))
	{
		_logger.LogInformation("Handling the custom exception here, will not pass  it on to further exception filters");
		context.Exception = null;
	}
}

The example is implemented in the controller as follows:

[HttpGet("getcustomexception")]
[ServiceFilter(typeof(CustomOneLoggingExceptionFilter))]
[ServiceFilter(typeof(CustomOneResourceFilter))]
public IEnumerable<string> GetWithCustomException()
{
	_logger.LogInformation("Executing Http Get before exception");
	throw new CustomException("Yes a great exception");
}

This can be called in a browser with the URL:
http://localhost:5000/api/testexception/getcustomexception

The global exception filter is never called.

aspnet5ExceptionFilters_02

Request with Exception with handled exception ordered

The Exception filter with the highest Order property value is executed first. We can force that the CustomOneLoggingExceptionFilter is executed last by setting the Order property to -1, less than the default Order value.

[HttpGet("getwithorder")]
[ServiceFilter(typeof(CustomOneLoggingExceptionFilter), Order = -1)]
[ServiceFilter(typeof(CustomOneResourceFilter))]
public IEnumerable<string> GetWithOrderedFiltered()
{
	_logger.LogInformation("Executing Http Get before exception");
	throw new Exception("Yes a great exception");
}

This can be tested in the browser with the URL:
http://localhost:5000/api/testexception/getwithorder

The executed order has been changed:

aspnet5ExceptionFilters_03

Links:

https://github.com/aspnet/Mvc/blob/229724c4eab3bf4fc8390deca9af7e451e5caee7/src/Microsoft.AspNet.Mvc.Core/Filters/ExceptionFilterAttribute.cs

https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Abstractions/Filters/IResourceFilter.cs

https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNet.Mvc.Abstractions/Filters


ASP.NET 5 multiple configurations without using environment variables

$
0
0

This article shows how to use multiple Json configuration files in ASP.NET 5 which can be used in CI for automatic deployment without requiring environment variables.

Code: https://github.com/damienbod/AspNet5Configuration

In ASP.NET 5, Json, XML and ini configuration files are supported per default. It is possible to create strongly type classes from the configuration files. It is also possible to load different configuration files depending on a run-time variable. An environment variable can be used for this which most of the examples or existing blogs demonstrates. Sometimes it is not possible to use an environment variable due to deployment requirements or restrictions. This example shows how to setup multiple configurations which can be used in automatic deployments for different target systems without using environment variables.

16.10.2015: Updated to ASP.NET 5 beta8

Application Global Configuration

A global application config file is used to set the application staging environment. The values in this file would typically be set from the installer, for example using a WIX silent installer with utils configuration.

{
     "StagingEnvironment ": "production"
}

This can then be used in the Startup.cs class in the constructor. The file is loaded and the StagingEnvironment value can now be used.

var globalbuilder = new ConfigurationBuilder()
                .SetBasePath(appEnv.ApplicationBasePath)
                         .AddJsonFile("globalconfig.json");
var globalConfiguration = globalbuilder.Build();

Application Configuration

The application configuration contains all the normal configurations which are required for the application. You can add many sections to the file, or a configuration class per file, or mix it as you require. I usually try to have just one file for all configuration objects when possible.

{
    "ApplicationConfiguration": {
        "MinimumLevel": 1,
        "AboutMessage": "This is the default configuration"
    }
}

The configuration section will be loaded into the ApplicationConfiguration class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace AspNet5Configuration.Configurations
{
    public class ApplicationConfiguration
    {
        public int MinimumLevel { get; set; }
        public string AboutMessage { get; set; }
    }
}

Now the application configuration can be loaded. The stagingEnvironment property loaded from the global configuration is used to load the correct configuration for the required staging target. This is optional, so if nothing is set or found, the default configuration file is used.

string stagingEnvironment = globalConfiguration["StagingEnvironment"];

var builder = new ConfigurationBuilder()
   .SetBasePath(appEnv.ApplicationBasePath)
   .AddJsonFile("config.json")
   .AddJsonFile($"config.{stagingEnvironment}.json", optional: true);
Configuration = builder.Build();

The configuration class can then be added the the services.

public void ConfigureServices(IServiceCollection services)
{
	services.Configure<ApplicationConfiguration>(
         Configuration.GetSection("ApplicationConfiguration"));
	services.AddMvc();
}

Using the Configuration

Now the configuration can be used. This is added in the constructor of the controller or the class where it is required. You cannot use the class directly, but by using the IOptions. The following example just displays the configuration value in the about HTTP Request method.

using System.Collections.Generic;
using AspNet5Configuration.Configurations;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.OptionsModel;

namespace AspNet5Configuration.Controllers
{
    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private IOptions<ApplicationConfiguration> _optionsApplicationConfiguration;
        public AboutController(IOptions<ApplicationConfiguration> o)
        {
            _optionsApplicationConfiguration = o;
        }

        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { _optionsApplicationConfiguration.Value.AboutMessage };
        }
    }
}

Testing the Configurations

The configuration can be viewed by requesting the api/about URL. This shows the used configuration.

Testing with “StagingEnvironment”: “production”

aspnet5_config_api_about_03

Testing with “StagingEnvironment”: “test”

aspnet5_config_api_about_02

Testing with “StagingEnvironment”: “unknown”

aspnet5_config_api_about_01

With this, it is possible to automatically deploy the application without using any environment variables and using different configurations for development, test, production or with different target systems.

Links:

http://www.elanderson.net/2015/10/configuration-in-asp-net-5/

http://www.mikesdotnetting.com/article/284/asp-net-5-configuration

http://gunnarpeipman.com/2014/11/asp-net-5-new-configuration-files-and-containers/

https://weblog.west-wind.com/posts/2015/Jun/03/Strongly-typed-AppSettings-Configuration-in-ASPNET-5

https://github.com/aspnet/Configuration

Introduction to ASP.NET 5, microsoft virtual academy


ASP.NET 5 updating to beta8 from older beta versions

$
0
0

This post just explains some of the things you need to do when updating to ASP.NET 5 beta8 from older versions. I have updated some existing ASP.NET 5 example projects to beta8.

The first thing you have to do is to update your dnvm, dnu and your dnx. This is the same as always. Documentation can be found here for windows, mac and linux. You must also update your Visual Studio Web tools because the new httpplatformhandler needs to be installed in the IIS Express. This also needs to be updated in the IIS dependiing how you deploy your projects.

Updating your MVC 6 project

I usually update the project.json file first. Here is an example of some of the beta8 references which are usually required in a MVC 6 project. The Microsoft.AspNet.IISPlatformHandler is a new dependency. The commands also need to be updated. Now the web command uses kestrel.

    "dependencies": {
        "Microsoft.AspNet.Diagnostics": "1.0.0-beta8",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-beta8",
        "Microsoft.AspNet.Mvc": "6.0.0-beta8",
        "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-beta8",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-beta8",
        "Microsoft.AspNet.StaticFiles": "1.0.0-beta8",
        "Microsoft.Framework.Logging": "1.0.0-beta8",
        "Microsoft.Framework.Logging.Console": "1.0.0-beta8",
        "Microsoft.Framework.Logging.Debug": "1.0.0-beta8",
        "NEST": "1.7.0",
        "Microsoft.Net.Http": "2.2.29",
        "Microsoft.AspNet.SignalR.Server": "3.0.0-beta8-15656",
        "NEST.Watcher": "1.0.0-beta2"
    },
    "commands": {
        "web": "Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:17202"
    },

Once everything is restored, you must replace your web.config file inside the wwwroot folder in your MVC 6 application. Replace it with the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<system.webServer>
		<handlers>
			<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
		</handlers>
		<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" forwardWindowsAuthToken="false" startupTimeLimit="3600" />
	</system.webServer>
</configuration>

The Startup class requires some changes as well. The main change is that the “app.UseIISPlatformHandler();” middleware is now required. The configuration and the UseExceptionHandler have also been updated/added. Here’s an example of a beta8 Startup class. You could also use yoeman to get a beta8 template.

using AspNet5Watcher.Configurations;
using AspNet5Watcher.SearchEngine;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.Configuration;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;

namespace AspNet5Watcher
{
    public class Startup
    {
        public IConfigurationRoot Configuration { get; set; }

        public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(appEnv.ApplicationBasePath)
                .AddJsonFile("config.json");
            Configuration = builder.Build();
        }
 
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ApplicationConfiguration>(Configuration.GetSection("ApplicationConfiguration"));

            services.AddMvc();
            services.AddSignalR(options =>
            {
                options.Hubs.EnableDetailedErrors = true;
            });

            services.AddScoped<SearchRepository, SearchRepository>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.MinimumLevel = LogLevel.Information;
            loggerFactory.AddConsole();
            loggerFactory.AddDebug();

            app.UseIISPlatformHandler();

            app.UseExceptionHandler("/Home/Error");

            app.UseStaticFiles();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            app.UseSignalR();
        }
    }
}

Then of course your code will require some changes. For example filters have been moved to a new namespace, or the configuration options uses the Value property not like before, or Entity Framework Entity class has replaced the Key property with HasKey. There are lots of breaking changing in this version, but it is a beta version. Here’s a list of breaking changes.

I also had an issue with ports and the kestrel server.

https://github.com/aspnet/Home/issues/993

Links

http://blogs.msdn.com/b/webdev/archive/2015/10/15/announcing-availability-of-asp-net-5-beta8.aspx

https://github.com/aspnet/Home/releases/tag/v1.0.0-beta8

http://www.iis.net/downloads/microsoft/httpplatformhandler

https://azure.microsoft.com/en-us/blog/announcing-the-release-of-the-httpplatformhandler-module-for-iis-8/



ASP.NET 5 MVC 6 Localization

$
0
0

This article shows some of the ways in which localization can be used in a MVC 6 ASP.NET 5 application.

Code: https://github.com/damienbod/AspNet5Localization

Localization Setup

The localization is configured in the setup class and can be used throughout the application per dependency injection. The AddLocalization method is used in the ConfigureServices to define the resources and localization. This can then be used in the Configure method. Here, the RequestLocalizationOptions can be defined and is added to the stack using the UseRequestLocalization method. You can also define different options as required, for example the Accept-Header provider could be removed or a custom provider could be added.

using System.Collections.Generic;
using System.Globalization;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Localization;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;

namespace AspNet5Localization
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddLocalization(options => options.ResourcesPath = "Resources");

            services.AddMvc()
                .AddViewLocalization()
                .AddDataAnnotationsLocalization();

            services.AddScoped<LanguageActionFilter>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.MinimumLevel = LogLevel.Information;
            loggerFactory.AddConsole();
            loggerFactory.AddDebug();

            var requestLocalizationOptions = new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture(new CultureInfo("en-US")),
                SupportedCultures = new List<CultureInfo>
                {
                    new CultureInfo("en-US"), 
                    new CultureInfo("de-CH"), 
                    new CultureInfo("fr-CH"), 
                    new CultureInfo("it-CH")
                },
                SupportedUICultures = new List<CultureInfo>
                {
                    new CultureInfo("en-US"), 
                    new CultureInfo("de-CH"), 
                    new CultureInfo("fr-CH"), 
                    new CultureInfo("it-CH")
                }
            };

            app.UseRequestLocalization(requestLocalizationOptions);

            app.UseIISPlatformHandler();

            app.UseStaticFiles();

            app.UseMvc();
        }
    }
}

The localization can be used for example in a MVC 6 controller. This is done by defining the IHtmlLocalizer with the name of your resx file(s). The resx files are defined in the folder defined in the Startup class AddLocalization method. The IHtmlLocalizer can then be used to return localized properties.

using System.Globalization;
using System.Threading;
using AspNet5Localization.Resources;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Localization;

namespace AspNet5Localization.Controllers
{
    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private IHtmlLocalizer<AmazingResource> _htmlLocalizer;

        public AboutController(IHtmlLocalizer<AmazingResource> localizer)
        {
            _htmlLocalizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _htmlLocalizer["Name"];
        }
    }
}

Setting the culture in the Query

The culture required by the client application can be set in the query using the ?culture=de-CH.

To test this, the application needs to be started in the console due to a Visual Studio Tooling bug.

Open the application in the src folder and

dnu restore

dnx web

Now the query localization can be tested or used as follows:

http://localhost:5000/api/About?culture=de-CH

http://localhost:5000/api/About?culture=it-CH 

Setting the culture in the Accept Header

The HTTP Request Accept-Language header can also be used to request from the server the required culture.

This is implemented as follows for the de-CH culture

GET http://localhost:5000/api/About HTTP/1.1

Accept: */*
Accept-Language: de-CH
Host: localhost:5000

Or implemented as follows for the it-CH culture

GET http://localhost:5000/api/About HTTP/1.1

Accept: */*
Accept-Language: it-CH
Host: localhost:5000

Setting the culture in the Request URL

The culture can also be set in the URL. This is not supported out of the box and you must implement this yourself for example using an action filter.

The action filter can be implemented as follows:

using System.Globalization;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Framework.Logging;

namespace AspNet5Localization
{
    public class LanguageActionFilter : ActionFilterAttribute
    {
        private readonly ILogger _logger;

        public LanguageActionFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("LanguageActionFilter");
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {         
            string culture = context.RouteData.Values["culture"].ToString();
            _logger.LogInformation($"Setting the culture from the URL: {culture}");

#if DNX451
            System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
            System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
#else
            CultureInfo.CurrentCulture = new CultureInfo(culture);
            CultureInfo.CurrentUICulture = new CultureInfo(culture);
#endif
            base.OnActionExecuting(context);
        }
    }
}

The culture value is defined as route data and this is then used to set the culture.

The action filter is then registered in the Startup ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
	services.AddLocalization(options => options.ResourcesPath = "Resources");

	services.AddMvc()
		.AddViewLocalization()
		.AddDataAnnotationsLocalization();

	services.AddScoped<LanguageActionFilter>();
}

This can then be used in a controller using an attribute routing parameter and applying the action filter to the controller class.

using System.Globalization;
using System.Threading;
using AspNet5Localization.Resources;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Localization;

namespace AspNet5Localization.Controllers
{
    [ServiceFilter(typeof(LanguageActionFilter))]
    [Route("api/{culture}/[controller]")]
    public class AboutWithCultureInRouteController : Controller
    {
        // http://localhost:5000/api/it-CH/AboutWithCultureInRoute
        // http://localhost:5000/api/fr-CH/AboutWithCultureInRoute

        private IHtmlLocalizer<AmazingResource> _htmlLocalizer;

        public AboutWithCultureInRouteController(IHtmlLocalizer<AmazingResource> localizer)
        {
            _htmlLocalizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _htmlLocalizer["Name"];
        }
    }
}

This can then be used as follows:

 http://localhost:5000/api/it-CH/AboutWithCultureInRoute
 
 http://localhost:5000/api/fr-CH/AboutWithCultureInRoute

This is very useful if you cannot rely on the browser culture.

Notes

Localization in MVC 6 is easy to use and flexible enough for most requirements. At present, the Visual Studio Tooling does not work and some of the implementation is not yet polished.

It is also not possible to add resx files to a MVC 6 application using Visual Studio. This will be supported. It is also possible to use the localization in Razor views if your not implementing a Javascript client. See the links underneath for further reading on this.

Links:

https://github.com/aspnet/Localization

https://github.com/aspnet/Tooling/issues/236

http://www.jerriepelser.com/blog/how-aspnet5-determines-culture-info-for-localization

http://www.vodovnik.com/2015/09/20/localization-in-asp-net-5-mvc-6/

https://github.com/WeebDo/WeebDo.CMF

https://github.com/joeaudette/cloudscribe

https://github.com/aspnet/Mvc/tree/dev/test/WebSites/LocalizationWebSite

Example of localization middleware for culture in route

http://weblogs.asp.net/jeff/beating-localization-into-submission-on-asp-net-5


Using DataAnnotations and Localization in ASP.NET 5 MVC 6

$
0
0

This article shows how ASP.NET 5 localization can be used together with data annotations. The data annotations are used to decorate the data model, and when HTTP POST/PUT (also PATCH) Requests are sent with model errors, the error message is returned localized in the request culture.

Code: https://github.com/damienbod/AspNet5Localization

Localization Setup

In the Startup class, the AddDataAnnotationsLocalization is added in the ConfigureServices method.

services.AddMvc()
   .AddViewLocalization()
   .AddDataAnnotationsLocalization();

Now a model class can be created and the data annotations can be used. The Length property in this example has a range attribute, with an ErrorMessageResourceName and an ErrorMessageResourceType property set. The ErrorMessageResourceType is used to define the resource itself using the type and the ErrorMessageResourceName is used to define the resource identifier. Only the Length property has localized error messages implemented.

using System.ComponentModel.DataAnnotations;
using AspNet5Localization.Resources;

namespace AspNet5Localization.Model
{
    public class Box
    {
        public long Id { get; set; }

        public double Height { get; set; }

        public double Width { get; set; }

        [Required(ErrorMessageResourceName = "BoxLengthRequired", ErrorMessageResourceType = typeof(AmazingResource))]
        [Range(1.0, 100.0, ErrorMessageResourceName = "BoxLengthRange", ErrorMessageResourceType = typeof(AmazingResource))]
        public double Length { get; set; }
    }
}

Now a MVC 6 controller can be created which uses the model class. This controller implements POST and PUT action methods, which uses the ModelState to validate the request. If the model is invalid, the error message is returned with a localized value.

using AspNet5Localization.Model;
using Microsoft.AspNet.Mvc;

namespace AspNet5Localization.Controllers
{
    [Route("api/[controller]")]
    public class BoxesController : Controller
    {
        [HttpGet("{id}")]
        public IActionResult Get(int id)
        {
            if (id == 0)
            {
                return HttpNotFound(id);
            }

            return Ok(new Box() { Id = id, Height = 10, Length = 10, Width=10 });
        }

        /// <summary>
        /// http://localhost:5000/api/boxes?culture=it-CH
        /// Content-Type: application/json
        /// 
        /// { "Id":7,"Height":10,"Width":10,"Length":1000}
        /// </summary>
        /// <param name="box"></param>
        /// <returns></returns>
        [HttpPost]
        public IActionResult Post([FromBody]Box box)
        {
            if (!ModelState.IsValid)
            {
                return HttpBadRequest(ModelState);
            }
            else
            {           
                string url = Url.RouteUrl("api/boxes", new { id = 11111 },
                    Request.Scheme, Request.Host.ToUriComponent());

                return Created(url, box);
            }
        }

        [HttpPut("{id}")]
        public IActionResult Put(int id, [FromBody]Box box)
        {
            if(id == 0)
            {
                return HttpNotFound(box);
            }

            if (!ModelState.IsValid)
            {
                return HttpBadRequest(ModelState);
            }
            else
            {
                return Ok(box);
            }
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            if (id == 0)
            {
                return HttpNotFound(id);
            }

            return Ok();
        }
    }
}

Now the POST method can be called in Fiddler or Postman. Underneath is an example of a HTTP POST Request using the it-CH culture. The length property is outside the range and will return an model state error.

http://localhost:5000/api/boxes?culture=it-CH

User-Agent: Fiddler
Host: localhost:5000
Content-Length: 46
Content-Type: application/json

{ "Id":7,"Height":10,"Width":10,"Length":1000}

HTTP Response with a it-CH localized error message:

HTTP/1.1 400 Bad Request
Date: Sat, 24 Oct 2015 17:15:28 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

{"Length":["The box length should be between 1 and a 100 it-CH"]}

Localization can be used in data annotations like previous versions of MVC or Web API and provides a simple way of validating your data inputs.

Links:

https://github.com/aspnet/Localization

https://github.com/aspnet/Tooling/issues/236

http://www.jerriepelser.com/blog/how-aspnet5-determines-culture-info-for-localization

http://www.vodovnik.com/2015/09/20/localization-in-asp-net-5-mvc-6/

https://github.com/WeebDo/WeebDo.CMF

https://github.com/joeaudette/cloudscribe

https://github.com/aspnet/Mvc/tree/dev/test/WebSites/LocalizationWebSite

Example of localization middleware for culture in route

http://weblogs.asp.net/jeff/beating-localization-into-submission-on-asp-net-5


OAuth2 Implicit Flow with Angular and ASP.NET 5 IdentityServer

$
0
0

This article shows how to implement the OAuth2 Implicit Flow with an Angular client and IdentityServer3 hosted in ASP.NET 5. The code was built using the example from the IdentityServer3.Samples. Thanks to everyone who helped in creating IdentityServer.

Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow

Setting up the resource server

The resource server is a simple Web API service implemented in MVC 6 in ASP.NET 5. A simple controller is used to implement CRUD methods for a SQLite database using Entity Framework 7.
The resource server in the code example is hosted at the URL: https://localhost:44303/

The Startup class configures the security middlerware. CORS is activated because the client application needs to access the resource. The security middleware is configured using the UseJwtBearerAuthentication method and also the RequiredScopesMiddleware implementation taken from the IdentityServer.samples. The UseJwtBearerAuthentication options defines where IdentityServer can be found to authorize HTTP requests.

using AspNet5SQLite.Model;
using AspNet5SQLite.Repositories;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.Configuration;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;

namespace AspNet5SQLite
{
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;

    public class Startup
    {
        public IConfigurationRoot Configuration { get; set; }

        public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(appEnv.ApplicationBasePath)
                .AddJsonFile("config.json");
            Configuration = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            var connection = Configuration["Production:SqliteConnectionString"];

            services.AddEntityFramework()
                .AddSqlite()
                .AddDbContext<DataEventRecordContext>(options => options.UseSqlite(connection));

            //Add Cors support to the service
            services.AddCors();

            var policy = new Microsoft.AspNet.Cors.Core.CorsPolicy();

            policy.Headers.Add("*");
            policy.Methods.Add("*");
            policy.Origins.Add("*");
            policy.SupportsCredentials = true;

            services.AddCors(x => x.AddPolicy("corsGlobalPolicy", policy));

            services.AddMvc();
            services.AddScoped<IDataEventRecordRepository, DataEventRecordRepository>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.MinimumLevel = LogLevel.Information;
            loggerFactory.AddConsole();
            loggerFactory.AddDebug();

            app.UseIISPlatformHandler();

            app.UseExceptionHandler("/Home/Error");

            app.UseCors("corsGlobalPolicy");

            app.UseStaticFiles();

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();

            app.UseJwtBearerAuthentication(options =>
            {
                options.Authority = "https://localhost:44300";
                options.Audience = "https://localhost:44300/resources";
                options.AutomaticAuthentication = true;
            });

            app.UseMiddleware<RequiredScopesMiddleware>(new List<string> { "dataEventRecords" });
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

RequiredScopesMiddleware is used to validate the scopes for each user. This was taken from the IdentityServer.samples project.

namespace AspNet5SQLite
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;

    using Microsoft.AspNet.Builder;
    using Microsoft.AspNet.Http;

    public class RequiredScopesMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IEnumerable<string> _requiredScopes;

        public RequiredScopesMiddleware(RequestDelegate next, List<string> requiredScopes)
        {
            _next = next;
            _requiredScopes = requiredScopes;
        }

        public async Task Invoke(HttpContext context)
        {
            if (context.User.Identity.IsAuthenticated)
            {
                if (!ScopePresent(context.User))
                {
                    context.Response.OnCompleted(Send403, context);
                    return;
                }
            }

            await _next(context);
        }
                
        private bool ScopePresent(ClaimsPrincipal principal)
        {
            foreach (var scope in principal.FindAll("scope"))
            {
                if (_requiredScopes.Contains(scope.Value))
                {
                    return true;
                }
            }

            return false;
        }

        private Task Send403(object contextObject)
        {
            var context = contextObject as HttpContext;
            context.Response.StatusCode = 403;

            return Task.FromResult(0);
        }
    }
}

The Controller class just requires the Authorize attribute to use the security middleware.


[Authorize]
[Route("api/[controller]")]
public class DataEventRecordsController : Controller
{
   // api implementation
}

Configuring the IdentityServer

IdentityServer is hosted in ASP.NET 5. This example is really just the basic configuration as in the example. The configuration has some important details when configuring the client, which must match the configuration in the resource server, and also the angular client. The IdentityServer in the code example is hosted at the URL: https://localhost:44300

The Startup class configures the server. This just adds the middleware and the SigningCertificate for HTTPS and the server is ready. Really simple for such powerful software.

using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using System.Security.Cryptography.X509Certificates;
using IdentityServer3.Core.Configuration;
using Microsoft.Dnx.Runtime;

namespace IdentityServerAspNet5
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDataProtection();
        }

        public void Configure(IApplicationBuilder app, IApplicationEnvironment env)
        {
            app.UseIISPlatformHandler();
            app.UseDeveloperExceptionPage();

            var certFile = env.ApplicationBasePath + "\\damienbodserver.pfx";

            var idsrvOptions = new IdentityServerOptions
            {
                Factory = new IdentityServerServiceFactory()
                                .UseInMemoryUsers(Users.Get())
                                .UseInMemoryClients(Clients.Get())
                                .UseInMemoryScopes(Scopes.Get()),

                SigningCertificate = new X509Certificate2(certFile, ""),
                AuthenticationOptions = new AuthenticationOptions
                {
                    EnablePostSignOutAutoRedirect = true
                }
            };

            app.UseIdentityServer(idsrvOptions);
        }
    }
}

The Users class is used to define the Users which can access the resource. This is a demo implementation and just defines the user, password and some default claims.

using System.Collections.Generic;
using System.Security.Claims;
using IdentityServer3.Core;
using IdentityServer3.Core.Services.InMemory;

namespace IdentityServerAspNet5
{
    static class Users
    {
        public static List<InMemoryUser> Get()
        {
            var users = new List<InMemoryUser>
            {
                new InMemoryUser{Subject = "48421156", Username = "damienbod", Password = "damienbod",
                    Claims = new Claim[]
                    {
                        new Claim(Constants.ClaimTypes.Name, "damienbod"),
                        new Claim(Constants.ClaimTypes.GivenName, "damienbod"),
                        new Claim(Constants.ClaimTypes.Email, "damien_bod@hotmail.com"),
                        new Claim(Constants.ClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(Constants.ClaimTypes.Role, "Developer")
                    }
                }
            };

            return users;
        }
    }
}

The following class is used to define the scopes. This is important and MUST match the scope defined in the resource server startup class implementation. This software uses the dataEventRecords scope for the resource server. Some standard scopes are also added, but are not used in the client.

using System.Collections.Generic;
using IdentityServer3.Core.Models;

namespace IdentityServerAspNet5
{
    public class Scopes
    {
        public static IEnumerable<Scope> Get()
        {
            return new[]
            {
                // standard OpenID Connect scopes
                StandardScopes.OpenId,
                StandardScopes.ProfileAlwaysInclude,
                StandardScopes.EmailAlwaysInclude,

                // API - access token will 
                // contain roles of user
                new Scope
                {
                    Name = "dataEventRecords",
                    DisplayName = "Data Event Records Scope",
                    Type = ScopeType.Resource,

                    Claims = new List<ScopeClaim>
                    {
                        new ScopeClaim("role")
                    }
                }
            };
        }
    }
}

The clients are defined in the following class. This must match the angular client implementation. The test server implements two test clients which activate the Implicit Flow. The RedirectUris are important and must match the client request EXACTLY, otherwise it will not work. The AllowedScopes also contain the dataEventRecords scope used for this application. The second client is the demo client from IdentityServer.samples.

using System.Collections.Generic;
using IdentityServer3.Core.Models;

namespace IdentityServerAspNet5
{
    public class Clients
    {
        public static List<Client> Get()
        {
            return new List<Client>
            {new Client
                {
                    ClientName = "angularclient",
                    ClientId = "angularclient",
                    Flow = Flows.Implicit,
                    RedirectUris = new List<string>
                    {
                        "https://localhost:44302/identitytestclient.html",
                        "https://localhost:44302/authorized"

                    },
                    PostLogoutRedirectUris = new List<string>
                    {
                        "https://localhost:44302/identitytestclient.html",
                        "https://localhost:44302/authorized"
                    },
                    AllowedScopes = new List<string>
                    {
                        "openid",
                        "email",
                        "profile",
                        "dataEventRecords"
                    }
                },
                new Client
                {
                    ClientName = "MVC6 Demo Client from Identity",
                    ClientId = "mvc6",
                    Flow = Flows.Implicit,
                    RedirectUris = new List<string>
                    {
                        "http://localhost:2221/",
                    },
                    PostLogoutRedirectUris = new List<string>
                    {
                        "http://localhost:2221/",
                    },
                    AllowedScopes = new List<string>
                    {
                        "openid",
                        "email",
                        "profile",
                        "dataEventRecords"
                    }
                }
            };
        }
    }
}

Implementing the Angular client

The angular client checks if it has a Bearer token to access the resource. If it doesn’t, it redirects to the IdentityServer where the user can logon. If successfully, it is redirected back to client, where it can then access the data in the resource server application. The Angular client in the code example is hosted at the URL: https://localhost:44302.

An AuthorizationInterceptor is used to intecept all http requests to the server and adds a Bearer token to the request, if its stored in the local storage. The angular-local-storage module is used to persist the token. The responseError is used to reset the local storage, if a 401 or a 403 is returned. This could be done better…

(function () {
    'use strict';

    var module = angular.module('mainApp');

    function AuthorizationInterceptor($q, localStorageService) {

        console.log("AuthorizationInterceptor created");

        var request = function (requestSuccess) {
            requestSuccess.headers = requestSuccess.headers || {};

            if (localStorageService.get("authorizationData") !== "") {
                requestSuccess.headers.Authorization = 'Bearer ' + localStorageService.get("authorizationData");
            }

            return requestSuccess || $q.when(requestSuccess);
        };

        var responseError = function(responseFailure) {

            console.log("console.log(responseFailure);");
            console.log(responseFailure);
            if (responseFailure.status === 403) {
                localStorageService.set("authorizationData", "");

            } else if (responseFailure.status === 401) {

                localStorageService.set("authorizationData", "");
            }

            return this.q.reject(responseFailure);
        };

        return {
            request: request,
            responseError: responseError
        }
    }

    module.service("authorizationInterceptor", [
            '$q',
            'localStorageService',
            AuthorizationInterceptor
    ]);

    module.config(["$httpProvider", function ($httpProvider) {
        $httpProvider.interceptors.push("authorizationInterceptor");
    }]);

})();

The AuthorizedController is used to redirect to the logon, and persist the token to the local storage. The redirect_uri parameter sent in the request token must match the client configuration on the server. The response_type must be set to token as we are using a javascript client. When the token is received in the hash from the IdentityServer, this is then saved to the local storage.

(function () {
	'use strict';

	var module = angular.module("mainApp");

	// this code can be used with uglify
	module.controller("AuthorizedController",
		[
			"$scope",
			"$log",
            "$window",
            "$state",
            "localStorageService",
			AuthorizedController
		]
	);

	function AuthorizedController($scope, $log, $window, $state, localStorageService) {
	    $log.info("AuthorizedController called");
		$scope.message = "AuthorizedController created";
	
        // TO force check always
	    localStorageService.set("authorizationData", "");
	    //localStorageService.get("authorizationData");
	    //localStorageService.set("authStateControl", "");
	    //localStorageService.get("authStateControl");

	    console.log(localStorageService.get("authorizationData"));

	    if (localStorageService.get("authorizationData") !== "") {
		    $scope.message = "AuthorizedController created logged on";
		   // console.log(authorizationData);
		    $state.go("overviewindex");
		} else {
		    console.log("AuthorizedController created, no auth data");
		    if ($window.location.hash) {
		        console.log("AuthorizedController created, has hash");
		        $scope.message = "AuthorizedController created with a code";

                    var hash = window.location.hash.substr(1);

		            var result = hash.split('&').reduce(function (result, item) {
		                var parts = item.split('=');
		                result[parts[0]] = parts[1];
		                return result;
		            }, {});

		            var token = "";
		            if (!result.error) {
		                if (result.state !== localStorageService.get("authStateControl")) {
		                    console.log("AuthorizedController created. no myautostate");                    
		                } else {
		                    localStorageService.set("authStateControl", "");
		                    console.log("AuthorizedController created. returning access token");
		                    token = result.access_token;
		                }
		            }

		            localStorageService.set("authorizationData", token);
		            console.log(localStorageService.get("authorizationData"));

		            $state.go("overviewindex");

		        } else {
		            $scope.message = "AuthorizedController time to log on";

		            var authorizationUrl = 'https://localhost:44300/connect/authorize';
		            var client_id = 'angularclient';
		            var redirect_uri = 'https://localhost:44302/authorized';
		            var response_type = "token";
		            var scope = "dataEventRecords";
		            var state = Date.now() + "" + Math.random();

		            localStorageService.set("authStateControl", state);
		            console.log("AuthorizedController created. adding myautostate: " + localStorageService.get("authStateControl"));
		          
		            var url =
                        authorizationUrl + "?" +
                        "client_id=" + encodeURI(client_id) + "&" +
                        "redirect_uri=" + encodeURI(redirect_uri) + "&" +
                        "response_type=" + encodeURI(response_type) + "&" +
                        "scope=" + encodeURI(scope) + "&" +
                        "state=" + encodeURI(state);
		            $window.location = url;
		        }
		}
	}
})();

Now the application can be used. The Visual Studio project is configured to start all three applications.

Once the application is started, you are redirected to the logon:
AngularClientIdentityServer3_01

You can then view the client requested scopes and allow the application to use the scopes:
AngularClientIdentityServer3_02

The application can access and use the resource server:
AngularClientIdentityServer3_03

Links:

https://github.com/IdentityServer/IdentityServer3

https://github.com/identityserver/IdentityServer3.Samples

http://leastprivilege.com/2015/07/22/the-state-of-security-in-asp-net-5-and-mvc-6-oauth-2-0-openid-connect-and-identityserver/


Using Elasticsearch with ASP.NET 5 dnxcore50

$
0
0

This article shows how to use Elasticsearch with ASP.NET 5, dnxcore or dnx451. ElasticsearchCrud ASP.NET 5 rc1 version is used for the Elasticsearch data access. ElasticsearchCrud supports dnxcore50, dnx451 and .NET with Elasticsearch 2.0.0. By supporting dnxcore50, the Elasticsearch API can now be run on windows, linux or a mac.

2015.11.19:
Updated to ASP.NET 5 rc1

Code: https://github.com/damienbod/AspNet5SearchWithElasticsearchCrud

To use Elasticsearch, add ElasticsearchCrud to the project.json file in the dependencies; “ElasticsearchCrud”: “2.0.0-beta8”

{
  "webroot": "wwwroot",
  "version": "1.0.0-*",

    "dependencies": {
        "ElasticsearchCrud": "2.0.1-rc1",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
        "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
        "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final"
    },

  "commands": {
    "web": "Microsoft.AspNet.Server.Kestrel"
  },

  "frameworks": {
    "dnx451": { },
    "dnxcore50": { }
  },

  "exclude": [
    "wwwroot",
    "node_modules"
  ],
  "publishExclude": [
    "**.user",
    "**.vspscc"
  ]
}

Here is a simple example of a search provider which implements a query_string query.

namespace AspNet5SearchWithElasticsearchCrud.Search
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using ElasticsearchCRUD;
    using ElasticsearchCRUD.Model.SearchModel;
    using ElasticsearchCRUD.Model.SearchModel.Queries;

    public class ElasticSearchProvider : ISearchProvider, IDisposable
	{
		public ElasticSearchProvider()
		{
			_context = new ElasticsearchContext(ConnectionString, _elasticSearchMappingResolver);
		}

		private const string ConnectionString = "http://localhost:9200/";
		private readonly IElasticsearchMappingResolver _elasticSearchMappingResolver = new ElasticsearchMappingResolver();
		private readonly ElasticsearchContext _context;

		public IEnumerable<Skill> QueryString(string term)
		{
			var results = _context.Search<Skill>(BuildQueryStringSearch(term));
			 return results.PayloadResult.Hits.HitsResult.Select(t => t.Source);
		}

		/*			
		{
		  "query": {
					"query_string": {
					   "query": "*"

					}
				}
		}
		 */
		private ElasticsearchCRUD.Model.SearchModel.Search BuildQueryStringSearch(string term)
		{
			var names = "";
			if (term != null)
			{
				names = term.Replace("+", " OR *");
			}

			var search = new ElasticsearchCRUD.Model.SearchModel.Search
			{
				Query = new Query(new QueryStringQuery(names + "*"))
			};

			return search;
		}

		public void AddUpdateEntity(Skill skill)
		{
			_context.AddUpdateDocument(skill, skill.Id);
			_context.SaveChanges();
		}

		public void UpdateSkill(long updateId, string updateName, string updateDescription)
		{
			var skill = _context.GetDocument<Skill>(updateId);
			skill.Updated = DateTime.UtcNow;
			skill.Name = updateName;
			skill.Description = updateDescription;
			_context.AddUpdateDocument(skill, skill.Id);
			_context.SaveChanges();
		}

		public void DeleteSkill(long deleteId)
		{
			_context.DeleteDocument<Skill>(deleteId);
			_context.SaveChanges();
		}

		private bool isDisposed;
		public void Dispose()
		{
			if (isDisposed)
			{
				isDisposed = true;
				_context.Dispose();
			}
		}
	}
}

The MVC 6 controller implements basic CRUD action methods using URL parameters.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;

namespace AspNet5SearchWithElasticsearchCrud.Controllers
{
    using AspNet5SearchWithElasticsearchCrud.Search;

    [Route("api/[controller]")]
    public class SearchController : Controller
    {
        readonly ISearchProvider _searchProvider;

        public SearchController(ISearchProvider searchProvider)
        {
            _searchProvider = searchProvider;
        }

        [HttpGet("{term}")]
        public IEnumerable<Skill> Search(string term)
        {
            return _searchProvider.QueryString(term);
        }

        [HttpPost("{id}/{name}/{description}")]
        public IActionResult Post(long id, string name, string description)
        {
            _searchProvider.AddUpdateEntity(
                new Skill
                    {
                        Created = DateTimeOffset.UtcNow,
                        Description = description,
                        Name = name,
                        Id = id
                    });

            string url = $"api/search/{id}/{name}/{description}";

            return Created(url, id);
        }

        [Microsoft.AspNet.Mvc.HttpPut("{id}/{updateName}/{updateDescription}")]
        public IActionResult Put(long id, string updateName, string updateDescription)
        {
            _searchProvider.UpdateSkill(id, updateName, updateDescription);

            return Ok();
        }


        // DELETE api/values/5
        [Microsoft.AspNet.Mvc.HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            _searchProvider.DeleteSkill(id);

            return new NoContentResult();
        }
    }
}

A new Skill document can be added using Fiddler.
asp_net5ElasticsearchCrud_created_01

And can also be viewed using the search with a query term as a parameter in the URL.

asp_net5ElasticsearchCrud_search_02

TODO ElasticsearchCrud:

– Clean up the dnx dependencies
– Port the Tests to XUnit

Links:

https://www.elastic.co/downloads/elasticsearch

https://www.nuget.org/packages/ElasticsearchCRUD/2.0.0-beta8

https://github.com/damienbod/ElasticsearchCRUD

http://damienbod.com/2014/09/22/elasticsearch-crud-net-provider/


ASP.NET 5 MVC 6 File Upload with MS SQL SERVER FileTable

$
0
0

This article shows how to upload and download files in ASP.NET 5 MVC 6 and save the files to a MS SQL Server using FileTable. The data access for the application is implemented in a separate project and EF7 migrations is used to setup the select logic for the database.

Code: https://github.com/damienbod/AspNet5FileUploadFileTable

Step 1: Settings up the database FileTable

A new database is created in MS SQL Server which has Filestreams enabled. This feature only works with windows authentication. Firstly if not already configured, the Filestream access level is set to 2.

EXEC sp_configure filestream_access_level, 2
RECONFIGURE
GO

Once this has been set, create a new directory to save the files. In this example, C:\damienbod\WebApiFileTable is used. Now execute the following command:

CREATE DATABASE WebApiFileTable
ON PRIMARY
(Name = WebApiFileTable,
FILENAME = 'C:\damienbod\WebApiFileTable\FTDB.mdf'),
FILEGROUP FTFG CONTAINS FILESTREAM
(NAME = WebApiFileTableFS,
FILENAME='C:\damienbod\WebApiFileTable\FS')
LOG ON
(Name = WebApiFileTableLog,
FILENAME = 'C:\damienbod\WebApiFileTable\FTDBLog.ldf')
WITH FILESTREAM (NON_TRANSACTED_ACCESS = FULL,
DIRECTORY_NAME = N'WebApiFileTable');
GO

Now you can check if your database settings are ok.

SELECT DB_NAME(database_id),
non_transacted_access,
non_transacted_access_desc
FROM sys.database_filestream_options;
GO

The database should be configured as follows:
FileTableWebApi01

Now create a table for the file uploads:

USE WebApiFileTable
GO
CREATE TABLE WebApiUploads AS FileTable
WITH
(FileTable_Directory = 'WebApiUploads_Dir');
GO

The files can be saved, deleted or updated using the following path:

\\{yourPCname}\{mssqlserver}\WebApiFileTable\WebApiUploads_Dir

The files can also be accessed using plain SQL.

INSERT INTO [dbo].[WebApiUploads]
([name],[file_stream])
SELECT
'NewFile.txt', * FROM OPENROWSET(BULK N'd:\NUnit-2.6.1.msi', SINGLE_BLOB) AS FileData
GO>

Step 2: Adding the Entity Framework 7 data access layer

A file description table is created for searching and returning multiple records. This is used to setup a download link and provide a small description of the file. To create the table, Entity Framework code first is used in this example.

Add Entity framework 7 to the project.json file in your project. The EF7 dependencies need to be added and also the ef commands.

{
    "version": "1.0.0-*",
    "description": "DataAccess Class Library",
    "authors": [ "damien.bowden" ],
    "tags": [ "" ],
    "projectUrl": "",
    "licenseUrl": "",

    "dependencies": {
        "EntityFramework.Core": "7.0.0-rc1-final",
        "EntityFramework.Commands": "7.0.0-rc1-final",
        "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
        "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
        "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
        "Microsoft.AspNet.Tooling.Razor": "1.0.0-rc1-final",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0-rc1-final",
        "Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc1-final",
        "Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
        "System.ComponentModel.Annotations": "4.0.11-beta-23516"
    },

    "frameworks": {
        "dnx451": { },
        "dnxcore50": { }
    },
    "commands": {
        "ef": "EntityFramework.Commands"
    }
}

An entity context class has to be created to use the database. This is used for the migrations and also the data access. The OnConfiguring method is required because the migrations are running in a separate project. This can be done in the startup class, if the migrations are in the same project as the MVC 6 application.

using Microsoft.Data.Entity;
using DataAccess.Model;
using Microsoft.Extensions.Configuration;

namespace DataAccess
{
    public class FileContext : DbContext
    {
        public DbSet<FileDescription> FileDescriptions { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<FileDescription>().HasKey(m => m.Id);
            base.OnModelCreating(builder);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var builder = new ConfigurationBuilder()
           .AddJsonFile("config.json")
           .AddEnvironmentVariables();
            var configuration = builder.Build();

            var sqlConnectionString = configuration["ApplicationConfiguration:SQLConnectionString"];

            optionsBuilder.UseSqlServer(sqlConnectionString);
        }
    }
}

The class used as the entity also needs to be created. The primary key for this class is also defined in the context class.

using System;
using System.ComponentModel.DataAnnotations;

namespace DataAccess.Model
{
    public class FileDescription
    {
        public int Id { get; set; }
        public string FileName { get; set; }
        public string Description { get; set; }
        public DateTime CreatedTimestamp { get; set; }
        public DateTime UpdatedTimestamp { get; set; }
        public string ContentType { get; set; }
    }
}

The connection string needs to be added to the config file which is used in the context. This is required for migrations and also running the application.

{
    "ApplicationConfiguration": {
        "SQLConnectionString": "Data Source=N275\\MSSQLSERVER2014;Initial Catalog=WebApiFileTable;Integrated Security=True;"
    }
}

The migrations can be created and the database can be updated. Open the application using the command line in the src folder where the project is defined.

>
> dnu restore
>
> dnx  ef migrations add testMigration
>
> dnx ef database update
>
>

If you don’t want to use EF7 migrations, you could just create the SQL table using plain TSQL.

USE [WebApiFileTable]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[FileDescription](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[FileName] [nvarchar](max) NULL,
	[Description] [nvarchar](max) NULL,
	[CreatedTimestamp] [datetime] NOT NULL,
	[UpdatedTimestamp] [datetime] NOT NULL,
	[ContentType] [nvarchar](max) NULL,
 CONSTRAINT [PK_dbo.FileDescription] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

Step 3: MVC 6 Single or Multiple file upload and download

The MVC 6 application is a simple project with razor views and a FileUpload MVC 6 controller to upload and download the files. The data access project is added as a reference in the project.json file in the dependencies. It does not matter if the dependencies uses sources from NuGet or from local projects.

{
  "version": "1.0.0-*",
  "compilationOptions": {
    "emitEntryPoint": true
  },

    "dependencies": {
        "EntityFramework.Core": "7.0.0-rc1-final",
        "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
        "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
        "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
        "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
        "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
        "Microsoft.AspNet.Tooling.Razor": "1.0.0-rc1-final",
        "Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc1-final",
        "Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
        "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc1-final",
        "DataAccess": "1.0.0"
    },

    "commands": {
        "web": "Microsoft.AspNet.Server.Kestrel"
    },

  "frameworks": {
    "dnx451": { },
    "dnxcore50": { }
  },

  "exclude": [
    "wwwroot",
    "node_modules"
  ],
  "publishExclude": [
    "**.user",
    "**.vspscc"
  ],
  "scripts": {
    "prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ]
  }
}

ASP.NET 5 provides the IFormFile class for file upload. This class is used inside the FileDescriptionShort, which is used for single or multiple file uploads.

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

	public string Description { get; set; }

	public string Name { get; set; }

	public ICollection<IFormFile> File { get; set; }
}

The FileUploadController has two action methods. The controller uses the default DI with constructor injection to add the dependencies. The UploadFiles action method uses the FileDescriptionShort class as a parameter. The method takes all the files and saves each file directly to the MS SQL Server FileTable. Then the file descriptions are saved to the database. The descriptions are used to list and download to files.

The file upload logic was built using the following two blogs:

http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6

http://dotnetthoughts.net/file-upload-in-asp-net-5-and-mvc-6/

Thanks for these articles.

namespace AspNet5FileUploadFileTable.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading.Tasks;

    using DataAccess;
    using DataAccess.Model;

    using Microsoft.AspNet.Http;
    using Microsoft.AspNet.Mvc;
    using Microsoft.Extensions.OptionsModel;
    using Microsoft.Net.Http.Headers;

    using FileResult = DataAccess.Model.FileResult;

    [Route("api/test")]
    public class FileUploadController : Controller
    {
        private readonly IFileRepository _fileRepository;
        private readonly IOptions<ApplicationConfiguration> _optionsApplicationConfiguration;

        public FileUploadController(IFileRepository fileRepository, IOptions<ApplicationConfiguration> o)
        {
            _fileRepository = fileRepository;
            _optionsApplicationConfiguration = o;
        }

        [Route("files")]
        [HttpPost]
        [ServiceFilter(typeof(ValidateMimeMultipartContentFilter))]
        public async Task<IActionResult> UploadFiles(FileDescriptionShort fileDescriptionShort)
        {
            var names = new List<string>();
            var contentTypes = new List<string>();
            if (ModelState.IsValid)
            {
                foreach (var file in fileDescriptionShort.File)
                {
                    if (file.Length > 0)
                    {
                        var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
                        contentTypes.Add(file.ContentType);
                        names.Add(fileName);

                        await file.SaveAsAsync(Path.Combine(_optionsApplicationConfiguration.Value.ServerUploadFolder, fileName));
                    }
                }
            }

            var files = new FileResult
                            {
                                FileNames = names,
                                ContentTypes = contentTypes,
                                Description = fileDescriptionShort.Description,
                                CreatedTimestamp = DateTime.UtcNow,
                                UpdatedTimestamp = DateTime.UtcNow,
                            };

            _fileRepository.AddFileDescriptions(files);

            return RedirectToAction("ViewAllFiles", "FileClient");
        }

        [Route("download/{id}")]
        [HttpGet]
        public FileStreamResult Download(int id)
        {
            var fileDescription = _fileRepository.GetFileDescription(id);

            var path = _optionsApplicationConfiguration.Value.ServerUploadFolder + "\\" + fileDescription.FileName;
            var stream = new FileStream(path, FileMode.Open);
            return  File(stream, fileDescription.ContentType);
        }
    }
}

The upload method also uses a service filter to validate the mime type. The ValidateMimeMultipartContentFilter class implements the ActionFilterAttribute which provides virtual methods which can be overridden. This attribute throws an exception, if the mime type is incorrect. A file upload requires a multipart content type.

using System;
using System.Net;

using Microsoft.AspNet.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace AspNet5FileUploadFileTable
{
    public class ValidateMimeMultipartContentFilter : ActionFilterAttribute
    {
        private readonly ILogger _logger;

        public ValidateMimeMultipartContentFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("ctor ValidateMimeMultipartContentFilter");
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogWarning("ClassFilter OnActionExecuting");

            if (!IsMultipartContentType(context.HttpContext.Request.ContentType))
            {
                // TODO improve this with 415 response, instead of 500.
                throw new Exception("UnsupportedMediaType:" + HttpStatusCode.UnsupportedMediaType.ToString());
            }

            base.OnActionExecuting(context);
        }

        private static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType) && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }
    }
}

All the required application configurations are implemented in the Startup class. Entity Framework, configuration, attribute, and class dependencies are defined here.

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace AspNet5FileUploadFileTable
{
    using AspNet5FileUploadFileTable.Controllers;

    using DataAccess;

    using Microsoft.Data.Entity;

    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; set; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ApplicationConfiguration>( Configuration.GetSection("ApplicationConfiguration"));

            var connection = Configuration["ApplicationConfiguration:SQLConnectionString"];

            services.AddEntityFramework()
                .AddSqlServer()
                .AddDbContext<FileContext>(options => options.UseSqlServer(connection));

            services.AddMvc();

            services.AddScoped<IFileRepository, FileRepository>();
            services.AddScoped<ValidateMimeMultipartContentFilter>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseIISPlatformHandler();

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
        public static void Main(string[] args) => WebApplication.Run<Startup>(args);
    }
}

The razor view implements a HTML form which is used to upload the files. The form attributes enctype and method are important for file upload, these should be defined as follows: enctype=”multipart/form-data” method=”post”

<!doctype html>
<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <form enctype="multipart/form-data" method="post" action="http://localhost:20828/api/test/files" id="ajaxUploadForm" novalidate="novalidate">

            <fieldset>
                <legend style="padding-top: 10px; padding-bottom: 10px;">Uploaded Form</legend>

                <div class="col-xs-12" style="padding: 10px;">
                    <div class="col-xs-4">
                        <label>Description</label>
                    </div>
                    <div class="col-xs-7">
                        <textarea rows="2" placeholder="Description" class="form-control" name="description" id="description"></textarea>
                    </div>
                </div>

                <div class="col-xs-12" style="padding: 10px;">
                    <div class="col-xs-4">
                        <label>Upload</label>
                    </div>
                    <div class="col-xs-7">
                        <input type="file" id="fileInput" name="file" multiple>
                    </div>
                </div>

                <div class="col-xs-12" style="padding: 10px;">
                    <div class="col-xs-4">
                        <input type="submit" value="Upload" id="ajaxUploadButton" class="btn">
                    </div>
                    <div class="col-xs-7">

                    </div>
                </div>

            </fieldset>

        </form>
    </body>
</html>

Testing the application

The application displays all the existing files which where uploaded when started.
aspnet5upload_01

If clicked, the file can be downloaded:

http://localhost:20828/api/test/download/{id}

The files can be uploaded as follows:
aspnet5upload_02

Conclusion

The application is relatively easy to implement and is simple to understand. This is a big improvement compared to the same application implemented in .NET 4.5 with Web API. One problem which exists is the configuration when using separate projects. The paths depends on where the project is run. Because the data access project config.json is used in the migrations and also the application when running, this is copied to both projects so that it always works, which is not very nice. Some other workarounds exists for this, see stack overflow.

Links:

http://dotnetthoughts.net/file-upload-in-asp-net-5-and-mvc-6/

http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6

http://damienbod.com/2014/04/08/web-api-file-upload-with-ms-sql-server-filetable

http://senvichet.com/how-to-upload-file-from-web-form-in-asp-net-5-mvc-6/


ASP.NET 5 MVC 6 API documentation using Swashbuckle Swagger

$
0
0

This article shows how to document your MVC 6 API using Swagger with Swashbuckle. Per default, it does not use your xml comments in the code and this needs to be configured if required.

Code: https://github.com/damienbod/AspNet5GeoElasticsearch

Step 1: Add the required NuGet packages to the dependencies in the project.json file.

 "dependencies": {

  "Swashbuckle.SwaggerGen": "6.0.0-rc1-final",
  "Swashbuckle.SwaggerUi": "6.0.0-rc1-final",

},

Step 2: Produce the .xml file which contains the xml comments when building. Click the produce outputs on build checkbox in your project file.

aspnet5Mvc6Swagger_01

Or set the ProduceOutputsOnBuild property in the project xproj file.

<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>

Step 3: Configure Swashbuckle.SwaggerGen in the Startup class ConfigureServices method.

You need to define your path to the comments xml file, which can be found in the artifacts folder. This should be saved in a config file.

The ConfigureSwaggerDocument with OperationFilter method is required so that the xml comments are added to the documentation, and also ConfigureSwaggerSchema with ModelFilter

private string pathToDoc =
	"C:\\git\\damienbod\\AspNet5GeoElasticsearch\\artifacts\\bin\\AspNet5GeoElasticsearch\\Debug\\dnx451\\AspNet5GeoElasticsearch.xml";
public void ConfigureServices(IServiceCollection services)
{
	// Add framework services.
	services.AddMvc();

	services.AddSwaggerGen();

	services.ConfigureSwaggerDocument(options =>
	{
		options.SingleApiVersion(new Info
		{
			Version = "v1",
			Title = "Geo Search API",
			Description = "A simple api to search using geo location in Elasticsearch",
			TermsOfService = "None"
		});
		options.OperationFilter(new Swashbuckle.SwaggerGen.XmlComments.ApplyXmlActionComments(pathToDoc));
	});

	services.ConfigureSwaggerSchema(options =>
	{
		options.DescribeAllEnumsAsStrings = true;
		options.ModelFilter(new Swashbuckle.SwaggerGen.XmlComments.ApplyXmlTypeComments(pathToDoc));
	});

	services.AddScoped<ISearchProvider, SearchProvider>();

}

Step 4: Use swagger in the Startup class Configure method.

UseSwaggerGen and UseSwaggerUi

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.AddConsole(Configuration.GetSection("Logging"));
	loggerFactory.AddDebug();
	app.UseBrowserLink();
	app.UseDeveloperExceptionPage();
	app.UseIISPlatformHandler();
	app.UseStaticFiles();
	app.UseMvc(routes =>
	{
		routes.MapRoute(
			name: "default",
			template: "{controller=Home}/{action=Index}/{id?}");
	});

	app.UseSwaggerGen();
	app.UseSwaggerUi();
}

Step 5: Create a Controller API with your documentation:

using Microsoft.AspNet.Mvc;
using AspNet5GeoElasticsearch.ElasticsearchApi;
using AspNet5GeoElasticsearch.Models;

using Newtonsoft.Json;
using Swashbuckle.SwaggerGen.Annotations;

namespace AspNet5GeoElasticsearch.Controllers
{
    /// <summary>
    /// This class is used as an api for the search requests.
    /// </summary>
    [Route("api/[controller]")]
    [Produces("application/json")]
    public class SearchController : Controller
    {
        private readonly ISearchProvider _searchProvider;

        public SearchController(ISearchProvider searchProvider)
        {
            _searchProvider = searchProvider;
        }

        /// <summary>
        /// This method returns the found documents from Elasticsearch
        /// </summary>
        /// <param name="maxDistanceInMeter">Distance in meters from your location</param>
        /// <param name="centerLongitude">center Longitude </param>
        /// <param name="centerLatitude">center Latitude </param>
        /// <returns>All the documents which were found</returns>
        [HttpGet]
        [Produces(typeof(MapModel))]
        [SwaggerResponse(System.Net.HttpStatusCode.OK, Type = typeof(MapModel))]
        [Route("GeoSearch")]
        public ActionResult Search(uint maxDistanceInMeter, double centerLongitude, double centerLatitude)
        {
            var searchResult = _searchProvider.SearchForClosest(maxDistanceInMeter, centerLongitude, centerLatitude);
            var mapModel = new MapModel
            {
                MapData = JsonConvert.SerializeObject(searchResult),
                CenterLongitude = centerLongitude,
                CenterLatitude = centerLatitude,
                MaxDistanceInMeter = maxDistanceInMeter
            };

            return Ok(mapModel);
        }

        /// <summary>
        /// Inits the Elasticsearch documents
        /// </summary>
        [HttpPost]
        [Route("InitData")]
        public ActionResult InitData()
        {
            initSearchEngine();
            return Ok();
        }

        private void initSearchEngine()
        {
            if (!_searchProvider.MapDetailsIndexExists())
            {
                _searchProvider.InitMapDetailMapping();
                _searchProvider.AddMapDetailData();
            }
        }
    }
}

This can then be viewed using ./swagger/ui

http://localhost:21453/swagger/ui/index.html

aspnet5Mvc6Swagger_02

Links:

https://github.com/domaindrivendev/Swashbuckle

https://github.com/domaindrivendev/Ahoy


OUT-OF-PROCESS Semantic Logging V2 with Elasticsearch

$
0
0

This article shows how to use Microsoft patterns & practices OUT-OF-PROCESS Semantic Logging Version 2 with an Elasticsearch sink. Version 2 has breaking changes compared to version 1.1 for the Elasticsearch sink; this has been removed from the standard SLAB NuGet package… Due to this, a custom sink configuration is required and also some copy paste deployment in the windows service.

Step 1: Install Elasticsearch

When installed, you can check that it is running by browsing http://localhost:9200/ (default installation)

Step 2: Install EnterpriseLibrary Semantic Logging as a service

Download EnterpriseLibrary.SemanticLogging.Service from NuGet (at present version 2.0.1406.1), extract and copy the files to some folder on your PC and install the service using the install-packages.ps1 file in powershell.

Download EnterpriseLibrary.SemanticLogging.Elasticsearch from NuGet (at present version 2.2.5) and copy the files to the EnterpriseLibrary.SemanticLogging.Service/tools directory.

EL_SLAB_Elasticsearch_01

Step 3: Add the Elasticsearch custom sink to the XML configuration file.

Open the SemanticLogging-svc.xml file and add a custom sink like shown below.

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/practices/2013/entlib/semanticlogging/etw"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://schemas.microsoft.com/practices/2013/entlib/semanticlogging/etw SemanticLogging-svc.xsd">
  
  <traceEventService/>

  <sinks>
    <customSink name="MyElasticsearchSink" type ="FullScale180.SemanticLogging.Sinks.ElasticsearchSink, FullScale180.SemanticLogging.Elasticsearch">
      <sources>
        <eventSource name="TestEvents" level="LogAlways" />
      </sources>
      <parameters>
		<parameter name="instanceName" type="System.String" value="damienbod" />
		<parameter name="connectionString" type="System.String" value="http://localhost:9200" />
		<parameter name="index" type="System.String" value="outofprocessslab" />
		<parameter name="type" type="System.String" value="webapitracing" />
		<parameter name="flattenPayload" type="System.Boolean" value="true" />
		<parameter name="bufferInterval" type="System.TimeSpan" value="00:00:30" />		
		<parameter name="bufferingCount" type="System.Int32" value="10" />		
		<parameter name="maxBufferSize" type="System.Int32" value="500" />	
		<parameter name="onCompletedTimeout" type="System.TimeSpan" value="00:05:00" /> 
		<parameter name="jsonGlobalContextExtension" type="System.String" value="null" />	
      </parameters>
    </customSink>
  </sinks>

</configuration>

Step 4: Install and start the service

Open the command line as admin and execute the following:

SemanticLogging-svc.exe -install

Now the service should be running and ready to log. This can be checked in the services. (services.msc)

Step 5: Create a test application

Create a simple console application and add the package

“EnterpriseLibrary.SemanticLogging”: “2.0.1406.1”

Add an EventSource class which matches the configuration in the SLAB service.

[EventSource(Name = "TestEvents")]
public class TestEvents : EventSource
{
	public static readonly TestEvents Log = new TestEvents();

	[Event(1, Message = "TestEvents Critical: {0}", Level = EventLevel.Critical)]
	public void Critical(string message)
	{
		if (IsEnabled()) WriteEvent(1, message);
	}

	[Event(2, Message = "TestEvents Error {0}", Level = EventLevel.Error)]
	public void Error(string message)
	{
		if (IsEnabled()) WriteEvent(2, message);
	}

	[Event(3, Message = "TestEvents Informational {0}", Level = EventLevel.Informational)]
	public void Informational(string message)
	{
		if (IsEnabled()) WriteEvent(3, message);
	}

	[Event(4, Message = "TestEvents LogAlways {0}", Level = EventLevel.LogAlways)]
	public void LogAlways(string message)
	{
		if (IsEnabled()) WriteEvent(4, message);
	}

	[Event(5, Message = "TestEvents Verbose {0}", Level = EventLevel.Verbose)]
	public void Verbose(string message)
	{
		if (IsEnabled()) WriteEvent(5, message);
	}

	[Event(6, Message = "TestEvents Warning {0}", Level = EventLevel.Warning)]
	public void Warning(string message)
	{
		if (IsEnabled()) WriteEvent(6, message);
	}
}

Add some log messages.

using System.Diagnostics.Tracing;
using Microsoft.Practices.EnterpriseLibrary.SemanticLogging;

namespace Slab.Elasticsearch.Console
{
    class Program
    {
        static void Main(string[] args)
        {
            OutOfProcessLogging();
            System.Console.ReadLine();
        }
        private static void OutOfProcessLogging()
        {
            TestEvents.Log.Critical("Hello world Out-Of-Process Critical");
            TestEvents.Log.Error("Hello world Out-Of-Process Error");
            TestEvents.Log.Informational("Hello world Out-Of-Process Informational");
        }
    }
}

Run the application and you should see logs in Elasticsearch:

EL_SLAB_Elasticsearch_02

Links:

https://github.com/fullscale180/slab-sinks

https://github.com/mspnp/semantic-logging

https://www.nuget.org/packages/EnterpriseLibrary.SemanticLogging.Service/

https://www.nuget.org/packages/EnterpriseLibrary.SemanticLogging.Elasticsearch/



Experiments with Entity Framework 7 and ASP.NET 5 MVC 6

$
0
0

The article shows some of the ways in which Entity Framework 7 can be used together with ASP.NET 5 MVC 6. Both packages run on dnxcore which makes it possible to run on Linux, Mac or Windows operating systems. The Entity Framework providers are implemented in separate data access projects using onion architecture.

Code: https://github.com/damienbod/AspNet5MultipleProject

Multiple projects using Onion architecture

Onion architecture is used in this example. The MVC 6 project is the outer ring and provides the infrastructure. The MVC 6 depends on the DomainModel project. The MVC 6 project uses the IoC to connect a data access provider implementation to the IDataAccessProvider interface. No direct references to the data access implementations exist, only to the DomainModel. No dependencies exist to the SQLite or MS SQL Server database. This is really useful. I could change the Provider to any other data access layer, for example MongoDB without much effort. The business logic will remain unchanged.

EF7_ASP_NET5_MVC_6_02

Extra layers could also be added to the application as long as each layer only has dependencies to layers towards the center of the onion. For example, I could use view model DTOs instead of using the EF POCO entities directly in the MVC 6 API service, or maybe application services if required. No dependencies exist to the providers, except in the outer layer.

The providers are added in the projects.json file

 "dependencies": {
        "DataAccessSqliteProvider": "1.0.0-*",
        "DataAccessMsSqlServerProvider": "1.0.0-*"
    },

And used in the Startup class.

public void ConfigureServices(IServiceCollection services)
{
	services.AddEntityFramework()
		.AddSqlite()
		.AddDbContext<DomainModelSqliteContext>();

	services.AddEntityFramework()
					.AddSqlServer()
					.AddDbContext<DomainModelMsSqlServerContext>();

	JsonOutputFormatter jsonOutputFormatter = new JsonOutputFormatter
	{
		SerializerSettings = new JsonSerializerSettings
		{
			ReferenceLoopHandling = ReferenceLoopHandling.Ignore
		}
	};

	services.AddMvc(
		options =>
		{
			options.OutputFormatters.Clear();
			options.OutputFormatters.Insert(0, jsonOutputFormatter);
		}
	);

	// Use a SQLite database
	services.AddScoped<IDataAccessProvider, DataAccessSqliteProvider>();

	// Use a MS SQL Server database
	//services.AddScoped<IDataAccessProvider, DataAccessMsSqlServerProvider>();
}

EF7 Migrations and configuration

To use Entity Framework 7 migrations, start the command line and use the ef command from Entity Framework 7 which is configured in the projects.json file. Usually the connection string is stored in a config file. Due to this, the config file is used in the DBContext in the OnConfiguring method.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
	var builder = new ConfigurationBuilder()
   .AddJsonFile("../config.json")
   .AddEnvironmentVariables();
	var configuration = builder.Build();

	var sqlConnectionString = configuration["DataAccessMsSqlServerProvider:ConnectionString"];

	optionsBuilder.UseSqlServer(sqlConnectionString);
}

Now the project can be used for the migrations. The ef command can be used to create the scripts and run the produced scripts in the database.

>
> dnu restore
>
> dnx  ef migrations add testMigration
>
> dnx ef database update
>
>

A problem with this approach, when using relative paths for you config files in the class library, the main application project needs to find a config file with the same relative path.

EF7 Shadow Properties

Shadow Properties is a nice feature from Entity Framework 7 which allows you to add fields in the database table which are not part of your model.

A shadow property can be added to the Entity in the OnModelCreating method of a DBContext inplementation using the Property method.

protected override void OnModelCreating(ModelBuilder builder)
{ 
	builder.Entity<DataEventRecord>().HasKey(m => m.DataEventRecordId);
	builder.Entity<SourceInfo>().HasKey(m => m.SourceInfoId);

	// shadow properties
	builder.Entity<DataEventRecord>().Property<DateTime>("UpdatedTimestamp");
	builder.Entity<SourceInfo>().Property<DateTime>("UpdatedTimestamp");

	base.OnModelCreating(builder); 
}

Here’s a model class

public class SourceInfo
{
	public long SourceInfoId { get; set; }
	
	public string Name { get; set; }

	public string Description { get; set; }

	public DateTime Timestamp { get; set; }

	public List<DataEventRecord> DataEventRecords { get; set; }
}

And the produced table in SQLite. You can see that the UpdatedTimestamp field has been added.
EF7_ASP_NET5_MVC_6_01

This can be used and set in the DBContext implementation. Here is an example setting the UpdatedTimestamp in the SaveChanges method.

public override int SaveChanges()
{
	ChangeTracker.DetectChanges();

	updateUpdatedProperty<SourceInfo>();
	updateUpdatedProperty<DataEventRecord>();

	return base.SaveChanges();
}

private void updateUpdatedProperty<T>() where T : class
{
	var modifiedSourceInfo =
		ChangeTracker.Entries<T>()
			.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);

	foreach (var entry in modifiedSourceInfo)
	{
		entry.Property("UpdatedTimestamp").CurrentValue = DateTime.UtcNow;
	}
}

This property can also be used in Linq requests:

_context.DataEventRecords.OrderByDescending(
    dataEventRecord => EF.Property<DateTime>(dataEventRecord, "UpdatedTimestamp")).ToList();

Another example for shadow properties can be found towards the end of this video tutorial: https://channel9.msdn.com/Events/ASPNET-Events/ASPNET-Fall-Sessions/Entity-Framework-7

EF 7 Include and Json Serialization

By using the Include statement, child entities can be returned together with the parent entities in one request. This can be implemented using the Include extension:

return _context.SourceInfos
        .Include(s => s.DataEventRecords)
        .OrderByDescending(srcInfo => EF.Property<DateTime>(srcInfo, "UpdatedTimestamp")).ToList();

If returning child parent entities directly in a Web API controller as JSON, you might possible get a circular dependency JSON serialization exception depending on your model. To prevent this, the ReferenceLoopHandling.Ignore needs to be configured in the default JsonOutputFormatter. This can be set in the Startup class in the ConfigureServices method.

JsonOutputFormatter jsonOutputFormatter = new JsonOutputFormatter
{
	SerializerSettings = new JsonSerializerSettings
	{
		ReferenceLoopHandling = ReferenceLoopHandling.Ignore
	}
};

services.AddMvc(
	options =>
	{
		options.OutputFormatters.Clear();
		options.OutputFormatters.Insert(0, jsonOutputFormatter);
	}
);

Testing with Fiddler

Here are some examples how you can test the application.

DataEventRecord with an existing SourceInfo

POST http://localhost:5000/api/dataeventrecords HTTP/1.1
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 135
Content-Type: application/json;

{
  "DataEventRecordId":3,
  "Name":"Funny data",
  "Description":"yes",
  "Timestamp":"2015-12-27T08:31:35Z",
  "SourceInfo":
  { 
    "SourceInfoId":1,
    "Name":"Beauty",
    "Description":"first Source",
    "Timestamp":"2015-12-23T08:31:35+01:00",
    "DataEventRecords":[]
  }, 
  "SourceInfoId": 1
}

DataEventRecord with a new SourceInfo

{
  "DataEventRecordId":7,
  "Name":"Funny data",
  "Description":"yes",
  "Timestamp":"2015-12-27T08:31:35Z",
  "SourceInfo":
  { 
    "SourceInfoId":0,
    "Name":"Beauty",
    "Description":"second Source",
    "Timestamp":"2015-12-23T08:31:35+01:00",
    "DataEventRecords":[]
  },
 "SourceInfoId":0
}

Get all SourceInfos

http://localhost:5000/api/dataeventrecords/SourceInfos?withChildren=true

[
  { 
    "SourceInfoId":1,
    "Name":"Beauty",
    "Description":"first Source",
    "Timestamp":"2015-12-23T08:31:35+01:00",
    "DataEventRecords":[
       { 
         "DataEventRecordId":1,
         "Name":"Funny data",
         "Description":"yes",
         "Timestamp":"2015-12-27T08:31:35+01:00",
         "SourceInfoId":1
       },
       {
         "DataEventRecordId":2,
         "Name":" second",
         "Description":"22dcee",
         "Timestamp":"2015-12-23T08:31:35+01:00",
         "SourceInfoId":1
       }
    ]
  }
]

Using Entity Framework 7 together with ASP.NET 5 MVC 6, it is really easy to create a API back-end for your application, if it fits your architecture, requirements. The two packages work really good together.

Links:

https://channel9.msdn.com/Events/ASPNET-Events/ASPNET-Fall-Sessions/Entity-Framework-7

http://ef.readthedocs.org/en/latest/modeling/shadow-properties.html

http://debugmode.net/2015/04/17/step-by-step-implementing-onion-architecture-in-asp-net-mvc-application/

http://jeffreypalermo.com/blog/the-onion-architecture-part-1/

http://damienbod.com/2015/08/30/asp-net-5-with-sqlite-and-entity-framework-7/

http://damienbod.com/2015/12/05/asp-net-5-mvc-6-file-upload-with-ms-sql-server-filetable/

http://www.bricelam.net/

https://github.com/dotnetcurry/building-asp.net-mvc-6-apps

http://weblogs.asp.net/jeff/ef7-rc-navigation-properties-and-lazy-loading

https://msdn.microsoft.com/magazine/mt614250


ASP.NET 5 with PostgreSQL and Entity Framework 7

$
0
0

This article shows how to use PostgreSQL with ASP.NET 5 using Entity Framework 7.

Code: https://github.com/damienbod/AspNet5MultipleProject

The PostgreSQL Entity Framework 7 provider can be downloaded as a NuGet package. Add the NuGet package EntityFramework7.Npgsql to your dependencies in the project.json file.

"dependencies": {
	"DomainModel": "1.0.0-*",
	"EntityFramework.Commands": "7.0.0-rc1-final",
	"EntityFramework7.Npgsql": "3.1.0-rc1-3",
	"Microsoft.AspNet.Diagnostics.Entity": "7.0.0-rc1-final",
	"Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-final",
	"Microsoft.Extensions.Configuration.Abstractions": "1.0.0-rc1-final",
	"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc1-final",
	"Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc1-final",
	"Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
	"Microsoft.Extensions.Logging": "1.0.0-rc1-final",
	"Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
	"Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final"
},
"commands": {
	"ef": "EntityFramework.Commands"
},

Create the context for Entity Framework 7 which is used for the PostgreSQL database.

namespace DataAccessPostgreSqlProvider
{ 
    using System;
    using System.Linq;

    using DomainModel.Model;

    using Microsoft.Data.Entity;
    using Microsoft.Extensions.Configuration;

    // >dnx . ef migration add testMigration
    public class DomainModelPostgreSqlContext : DbContext
    {
        public DbSet<DataEventRecord> DataEventRecords { get; set; }

        public DbSet<SourceInfo> SourceInfos { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<DataEventRecord>().HasKey(m => m.DataEventRecordId);
            builder.Entity<SourceInfo>().HasKey(m => m.SourceInfoId);

            // shadow properties
            builder.Entity<DataEventRecord>().Property<DateTime>("UpdatedTimestamp");
            builder.Entity<SourceInfo>().Property<DateTime>("UpdatedTimestamp");

            base.OnModelCreating(builder);
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var builder = new ConfigurationBuilder()
           .AddJsonFile("../config.json")
           .AddEnvironmentVariables();
            var configuration = builder.Build();

            var sqlConnectionString = 
               configuration["DataAccessPostgreSqlProvider:ConnectionString"];

            optionsBuilder.UseNpgsql(sqlConnectionString);
        }

        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges();

            updateUpdatedProperty<SourceInfo>();
            updateUpdatedProperty<DataEventRecord>();

            return base.SaveChanges();
        }

        private void updateUpdatedProperty<T>() where T : class
        {
            var modifiedSourceInfo =
                ChangeTracker.Entries<T>()
                    .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);

            foreach (var entry in modifiedSourceInfo)
            {
                entry.Property("UpdatedTimestamp").CurrentValue = DateTime.UtcNow;
            }
        }
    }
}

Add the POCO classes to use as entities.

public class DataEventRecord
{
	public long DataEventRecordId { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }
	public DateTime Timestamp { get; set; }
	public SourceInfo SourceInfo { get; set; }
	public int SourceInfoId { get; set; }
}

public class SourceInfo
{
	public long SourceInfoId { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }
	public DateTime Timestamp { get; set; }
	public List<DataEventRecord> DataEventRecords { get; set; }
}

The connection string can be added in the OnConfiguring method in the class which implements the DbContext, or via dependency injection in the constructor using the options. The connection string property User ID needs to exist in the database and should have the create database rights.

 "DataAccessPostgreSqlProvider": {
        "ConnectionString": "User ID=damienbod;Password=1234;Host=localhost;Port=5432;Database=damienbod;Pooling=true;"
    }

Open pgAdmin to configure the user in PostgreSQL.

EF7_PostgreSQL_01

Right click your user and click propeties to set the password

EF7_PostgreSQL_02

Set up the migration scripts now. Open the command line in the same folder where the ef command is defined in the project.json file.

>
> dnx ef migrations add testPG
>
> dnx ef database update
>

The database should now exist. Add Entity Framework 7 to the ASP.NET 5 application in the Startup class. A DataAccessPostgreSqlProvider class with an interface is used to access the context from anywhere else in the application.

public void ConfigureServices(IServiceCollection services)
{
	services.AddEntityFramework()
		.AddNpgsql()
		.AddDbContext<DomainModelPostgreSqlContext>();

	JsonOutputFormatter jsonOutputFormatter = new JsonOutputFormatter
	{
		SerializerSettings = new JsonSerializerSettings
		{
			ReferenceLoopHandling = ReferenceLoopHandling.Ignore
		}
	};

	services.AddMvc(
		options =>
		{
			options.OutputFormatters.Clear();
			options.OutputFormatters.Insert(0, jsonOutputFormatter);
		}
	);

	// Use a PostgreSQL database
	services.AddScoped<IDataAccessProvider, DataAccessPostgreSqlProvider>();
}

Now the PostgreSQL provider can be used in a MVC 6 controller using construction injection.

namespace AspNet5MultipleProject.Controllers
{
    using System.Collections.Generic;

    using DomainModel;
    using DomainModel.Model;

    using Microsoft.AspNet.Mvc;

    using Newtonsoft.Json;

    [Route("api/[controller]")]
    public class DataEventRecordsController : Controller
    {
        private readonly IDataAccessProvider _dataAccessProvider;

        public DataEventRecordsController(IDataAccessProvider dataAccessProvider)
        {
            _dataAccessProvider = dataAccessProvider;
        }

        [HttpGet]
        public IEnumerable<DataEventRecord> Get()
        {
            return _dataAccessProvider.GetDataEventRecords();
        }

        [HttpGet]
        [Route("SourceInfos")]
        public IEnumerable<SourceInfo> GetSourceInfos(bool withChildren)
        {
            return _dataAccessProvider.GetSourceInfos(withChildren);
        }

        [HttpGet("{id}")]
        public DataEventRecord Get(long id)
        {
            return _dataAccessProvider.GetDataEventRecord(id);
        }

        [HttpPost]
        public void Post([FromBody]DataEventRecord value)
        {
            _dataAccessProvider.AddDataEventRecord(value);
        }

        [HttpPut("{id}")]
        public void Put(long id, [FromBody]DataEventRecord value)
        {
            _dataAccessProvider.UpdateDataEventRecord(id, value);
        }

        [HttpDelete("{id}")]
        public void Delete(long id)
        {
            _dataAccessProvider.DeleteDataEventRecord(id);
        }
    }
}

The controller api can be called using Fiddler:

POST http://localhost:5000/api/dataeventrecords HTTP/1.1
User-Agent: Fiddler
Host: localhost:5000
Content-Length: 135
Content-Type: application/json;
 
{
  "DataEventRecordId":3,
  "Name":"Funny data",
  "Description":"yes",
  "Timestamp":"2015-12-27T08:31:35Z",
   "SourceInfo":
  { 
    "SourceInfoId":0,
    "Name":"Beauty",
    "Description":"second Source",
    "Timestamp":"2015-12-23T08:31:35+01:00",
    "DataEventRecords":[]
  },
 "SourceInfoId":0 
}

The data can be viewed now in PostgreSQL.

EF7_PostgreSQL_03

PostgreSQL is a great choice for a rational database if you what to support multiple runtime environments.

Links:

http://www.postgresql.org

http://www.pgadmin.org/

https://github.com/npgsql/npgsql

http://www.npgsql.org/doc/ef7.html

http://damienbod.com/2016/01/07/experiments-with-entity-framework-7-and-asp-net-5-mvc-6/


ASP.NET Core 1.0 using SQL Localization

$
0
0

This article shows how to use SQL localization in ASP.NET Core using an SQL database. The SQL localization in the demo uses Entity Framework Core to access a SQLite database. This can be configured to use any EF core provider.

Code: https://github.com/damienbod/AspNet5Localization

Library: https://github.com/damienbod/AspNet5Localization/tree/master/AspNet5Localization/src/Localization.SqlLocalizer

Note 2016.01.29: This application is using ASP.NET Core 1.0 rc2. At present this has not been released, so you need to get the unstable version from MyGet, if you want to run it.

Using the SQL Localization

To use the SQL localization, the library needs to be added to the dependencies in the project.json file. The library is called Localization.SqlLocalizer.

"dependencies": {

	"Localization.SqlLocalizer": "1.0.0.0",

	"Microsoft.EntityFrameworkCore.Commands": "1.0.0-rc2-*",
	"Microsoft.EntityFrameworkCore.Sqlite": "1.0.0-rc2-*",
	"Microsoft.EntityFrameworkCore": "1.0.0-rc2-*",
	"Microsoft.EntityFrameworkCore.Relational.Design": "1.0.0-rc2-*",
	"Microsoft.EntityFrameworkCore.Sqlite.Design": "1.0.0-rc2-*",
	"Microsoft.EntityFrameworkCore.Relational": "1.0.0-rc2-*",
	"Microsoft.Data.Sqlite": "1.0.0-rc2-*",

	"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.IISPlatformHandler": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Localization": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Mvc.Localization": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Mvc.Razor": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Tooling.Razor": "1.0.0-rc2-*",
	"Microsoft.AspNetCore.Mvc.Core": "1.0.0-rc2-*",

	"Microsoft.Extensions.CodeGenerators.Mvc": "1.0.0-rc2-*",
	"Microsoft.Extensions.Configuration.FileProviderExtensions": "1.0.0-rc2-*",
	"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-*",
	"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0-rc2-*",
	"Microsoft.Extensions.Logging": "1.0.0-rc2-*",
	"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-*",
	"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-*",

	"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc2-*",
	"Microsoft.Extensions.Globalization.CultureInfoCache": "1.0.0-rc2-*",
	"Microsoft.Extensions.Localization.Abstractions": "1.0.0-rc2-*",
	"Microsoft.Extensions.Localization": "1.0.0-rc2-*",
	"Microsoft.Extensions.CodeGeneration": "1.0.0-rc2-*",
	"System.Reflection": "4.1.0-rc2-*"
},

This can then be added in the Startup class ConfigureServices method. The AddSqlLocalization method requires that the LocalizationModelContext class is configured in an Entity Framework service extension. In this example, SQLite is used as the provider for the LocalizationModelContext context class. The rest of the localization can be configured as required. The example supports en-US, de-CH, fr-CH, it-CH.

public void ConfigureServices(IServiceCollection services)
{
	// init database for localization
	var sqlConnectionString = Configuration["DbStringLocalizer:ConnectionString"];

	services.AddEntityFramework()
		 .AddSqlite()
		 .AddDbContext<LocalizationModelContext>(
			 options => options.UseSqlite(sqlConnectionString));

	// Requires that LocalizationModelContext is defined
	services.AddSqlLocalization();

	services.AddMvc()
		.AddViewLocalization()
		.AddDataAnnotationsLocalization();

	services.AddScoped<LanguageActionFilter>();

	services.Configure<RequestLocalizationOptions>(
		options =>
			{
				var supportedCultures = new List<CultureInfo>
				{
					new CultureInfo("en-US"),
					new CultureInfo("de-CH"),
					new CultureInfo("fr-CH"),
					new CultureInfo("it-CH")
				};

				options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
				options.SupportedCultures = supportedCultures;
				options.SupportedUICultures = supportedCultures;
			});
}

The localization also needs to be added in the Configure method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.AddConsole();
	loggerFactory.AddDebug();

	var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
	app.UseRequestLocalization(locOptions.Value);

	var options = new IISPlatformHandlerOptions();
	options.AuthenticationDescriptions.Clear();
	app.UseIISPlatformHandler(options);

	app.UseStaticFiles();

	app.UseMvc();
}

The database now needs to be created. If using SQLite, a creation sql script is provided in the SqliteCreateLocalizationRecord.sql file. If using a different database, this needs to be created. I have provided no migration scripts. The SQLite script can be executed in Firefox using the SQLite Manager.

CREATE TABLE "LocalizationRecord" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_DataEventRecord" PRIMARY KEY AUTOINCREMENT,
    "Key" TEXT,
	"ResourceKey" TEXT,
    "Text" TEXT,
    "LocalizationCulture" TEXT,
    "UpdatedTimestamp" TEXT NOT NULL
)

The database should now exist. I have added some basic demo rows.

aspnetcore_loc_sql_01

The default configuration for the SQL Localization uses the name of the resource, then the resource key (which could be the default language text if you follow the recommendations from Microsoft), and then the culture. A separate field in the database exists for each of these properties. If the localization is not found, the searched key is returned. Here’s an example of a localization which was not found and returned to the UI.

ASP.NET Core MVC controller:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace AspNet5Localization.Controllers
{

    [ServiceFilter(typeof(LanguageActionFilter))]
    [Route("api/{culture}/[controller]")]
    public class AboutWithCultureInRouteController : Controller
    {
        // http://localhost:5000/api/it-CH/AboutWithCultureInRoute
        // http://localhost:5000/api/fr-CH/AboutWithCultureInRoute

        private readonly IStringLocalizer<SharedResource> _localizer;


        public AboutWithCultureInRouteController(IStringLocalizer<SharedResource> localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _localizer["Name"];
        }
    }
}

URL used:

http://localhost:5000/api/fr-CH/AboutWithCultureInRoute

Result: ResoureKey: SharedResource, Key: Name, LocalizationCulture: fr-CH

SharedResource.Name.fr-CH

This should make it easy to find and add a missing localization.

Configuring the SQL Localization

The SQL Localization can also be configured to use different keys to search for the localization in the database. This can be configured in the Startup class using the services.AddSqlLocalization and adding the options parameter.

The SqlLocalizationOptions has two properties, UseTypeFullNames and UseOnlyPropertyNames. If the UseOnlyPropertyNames is true, only the property name is used in the database as the key with a ResourceKey global. You could also configure it to use FullNames as a key by setting the UseTypeFullNames. If this is set, the full type name is required in the ResourceKey property in the database.

public class SqlLocalizationOptions
{
	/// <summary>
	/// If UseOnlyPropertyNames is false, this property can be used to define keys with full type names or just the name of the class
	/// </summary>
	public bool UseTypeFullNames { get; set; }

	/// <summary>
	/// This can be used to use only property names to find the keys
	/// </summary>
	public bool UseOnlyPropertyNames { get; set; }
}

Example using options in the Startup class:

var sqlConnectionString = Configuration["DbStringLocalizer:ConnectionString"];

services.AddEntityFramework()
	 .AddSqlite()
	 .AddDbContext<LocalizationModelContext>(
		 options => options.UseSqlite(sqlConnectionString));

// Requires that LocalizationModelContext is defined
services.AddSqlLocalization(options =>  options.UseTypeFullNames = true);

Used Controller for the HTTP request:

using System.Globalization;
using System.Threading;

namespace AspNet5Localization.Controllers
{
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Localization;
    using Microsoft.Extensions.Localization;

    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private readonly IStringLocalizer<SharedResource> _localizer;
        private readonly IStringLocalizer<AboutController> _aboutLocalizerizer;

        public AboutController(IStringLocalizer<SharedResource> localizer, IStringLocalizer<AboutController> aboutLocalizerizer)
        {
            _localizer = localizer;
            _aboutLocalizerizer = aboutLocalizerizer;
        }

        [HttpGet]
        public string Get()
        {
            // _localizer["Name"] 
            return _aboutLocalizerizer["AboutTitle"];
        }
    }
}

Url:

http://localhost:5000/api/about?culture=it-CH

Result: You can see from the result, that the SQL localization searched for the localization using the FullName. ResoureKey: AspNet5Localization.Controllers.AboutController, Key: AboutTitle, LocalizationCulture: it-CH

AspNet5Localization.Controllers.AboutController.AboutTitle.it-CH

SQL Localization in detail

The SQL localization library uses extension methods to provide its service which can be used in the Startup class of your application. The library depends on Entity Framework Core. Any database provider can be used for this.

using System;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Localization;

namespace Microsoft.Extensions.DependencyInjection
{
    using global::Localization.SqlLocalizer;
    using global::Localization.SqlLocalizer.DbStringLocalizer;

    /// <summary>
    /// Extension methods for adding localization servics to the DI container.
    /// </summary>
    public static class SqlLocalizationServiceCollectionExtensions
    {
        /// <summary>
        /// Adds services required for application localization.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
        /// <returns>The <see cref="IServiceCollection"/>.</returns>
        public static IServiceCollection AddSqlLocalization(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            return AddSqlLocalization(services, setupAction: null);
        }

        /// <summary>
        /// Adds services required for application localization.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
        /// <param name="setupAction">An action to configure the <see cref="LocalizationOptions"/>.</param>
        /// <returns>The <see cref="IServiceCollection"/>.</returns>
        public static IServiceCollection AddSqlLocalization(
            this IServiceCollection services,
            Action<SqlLocalizationOptions> setupAction)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            services.TryAdd(new ServiceDescriptor(
                typeof(IStringLocalizerFactory),
                typeof(SqlStringLocalizerFactory),
                ServiceLifetime.Singleton));
            services.TryAdd(new ServiceDescriptor(
                typeof(IStringLocalizer),
                typeof(SqlStringLocalizer),
                ServiceLifetime.Singleton));

            if (setupAction != null)
            {
                services.Configure(setupAction);
            }
            return services;
        }
    }
}

The SqlStringLocalizerFactory class implements the IStringLocalizerFactory which is responsible for the database access. The database is only used for the first request of each resource. This means better performance after this, but new translations are read only after an application restart.

namespace Localization.SqlLocalizer.DbStringLocalizer
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Extensions.Localization;
    using Microsoft.Extensions.Options;
    using Microsoft.Extensions.PlatformAbstractions;

    public class SqlStringLocalizerFactory : IStringLocalizerFactory
    {
        private readonly LocalizationModelContext _context;
        private readonly ConcurrentDictionary<string, IStringLocalizer> _resourceLocalizations = new ConcurrentDictionary<string, IStringLocalizer>();
        private readonly IOptions<SqlLocalizationOptions> _options;
        private const string Global = "global";

        public SqlStringLocalizerFactory(
           LocalizationModelContext context,
           IApplicationEnvironment applicationEnvironment,
           IOptions<SqlLocalizationOptions> localizationOptions)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(LocalizationModelContext));
            }

            if (applicationEnvironment == null)
            {
                throw new ArgumentNullException(nameof(applicationEnvironment));
            }

            if (localizationOptions == null)
            {
                throw new ArgumentNullException(nameof(localizationOptions));
            }

            _options = localizationOptions;
            _context = context;
        }

        public IStringLocalizer Create(Type resourceSource)
        {
            SqlStringLocalizer sqlStringLocalizer;

            if (_options.Value.UseOnlyPropertyNames)
            {
                if (_resourceLocalizations.Keys.Contains(Global))
                {
                    return _resourceLocalizations[Global];
                }

                sqlStringLocalizer = new SqlStringLocalizer(GetAllFromDatabaseForResource(Global), Global);
                return _resourceLocalizations.GetOrAdd(Global, sqlStringLocalizer);
                
            }

            if (_options.Value.UseTypeFullNames)
            {
                if (_resourceLocalizations.Keys.Contains(resourceSource.FullName))
                {
                    return _resourceLocalizations[resourceSource.FullName];
                }

                sqlStringLocalizer = new SqlStringLocalizer(GetAllFromDatabaseForResource(resourceSource.FullName), resourceSource.FullName);
                _resourceLocalizations.GetOrAdd(resourceSource.FullName, sqlStringLocalizer);
            }


            if (_resourceLocalizations.Keys.Contains(resourceSource.Name))
            {
                return _resourceLocalizations[resourceSource.Name];
            }

            sqlStringLocalizer = new SqlStringLocalizer(GetAllFromDatabaseForResource(resourceSource.Name), resourceSource.Name);
            return _resourceLocalizations.GetOrAdd(resourceSource.Name, sqlStringLocalizer);
        }

        public IStringLocalizer Create(string baseName, string location)
        {
            if (_resourceLocalizations.Keys.Contains(baseName + location))
            {
                return _resourceLocalizations[baseName + location];
            }

            var sqlStringLocalizer = new SqlStringLocalizer(GetAllFromDatabaseForResource(baseName + location), baseName + location);
            return _resourceLocalizations.GetOrAdd(baseName + location, sqlStringLocalizer);
        }

        private Dictionary<string, string> GetAllFromDatabaseForResource(string resourceKey)
        {
            return _context.LocalizationRecords.Where(data => data.ResourceKey == resourceKey).ToDictionary(kvp => (kvp.Key + "." + kvp.LocalizationCulture), kvp => kvp.Text);
        }
    }
}

The SqlStringLocalizer implements the IStringLocalizer. This is used as a singleton for the application as it is only GET resources.

namespace Localization.SqlLocalizer.DbStringLocalizer
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using Microsoft.Extensions.Localization;

    public class SqlStringLocalizer : IStringLocalizer
    {
        private readonly Dictionary<string, string> _localizations;

        private readonly string _resourceKey;
        public SqlStringLocalizer(Dictionary<string, string> localizations, string resourceKey)
        {
            _localizations = localizations;
            _resourceKey = resourceKey;
        }
        public LocalizedString this[string name]
        {
            get
            {
                return new LocalizedString(name, GetText(name));
            }
        }

        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                return new LocalizedString(name, GetText(name));
            }
        }

        public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
        {
            throw new NotImplementedException();
        }

        public IStringLocalizer WithCulture(CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        private string GetText(string key)
        {

#if DNX451
            var culture = System.Threading.Thread.CurrentThread.CurrentCulture.ToString();
#else
             var culture = CultureInfo.CurrentCulture.ToString();
#endif
            string computedKey = $"{key}.{culture}";

            string result;
            if (_localizations.TryGetValue(computedKey, out result))
            {
                return result;
            }
            else
            {
                return _resourceKey + "." + computedKey;
            }
        }
    }
}

The LocalizationModelContext class is the Entity Framework Core DbContext implementation. This uses the LocalizationRecord model class for data access. A shadow property is used for the database UpdatedTimestamp which will be used when updating with localization database imports.

namespace Localization.SqlLocalizer.DbStringLocalizer
{
    using System;
    using System.Linq;

    using Microsoft.EntityFrameworkCore;

    // >dnx ef migration add LocalizationMigration
    public class LocalizationModelContext : DbContext
    {
        public DbSet<LocalizationRecord> LocalizationRecords { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<LocalizationRecord>().HasKey(m => m.Id);
            //builder.Entity<LocalizationRecord>().HasKey(m => m.LocalizationCulture + m.Key);

            // shadow properties
            builder.Entity<LocalizationRecord>().Property<DateTime>("UpdatedTimestamp");

            base.OnModelCreating(builder);
        }

        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges();
            updateUpdatedProperty<LocalizationRecord>();
            return base.SaveChanges();
        }

        private void updateUpdatedProperty<T>() where T : class
        {
            var modifiedSourceInfo =
                ChangeTracker.Entries<T>()
                    .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);

            foreach (var entry in modifiedSourceInfo)
            {
                entry.Property("UpdatedTimestamp").CurrentValue = DateTime.UtcNow;
            }
        }
    }
}

Next Steps

The application could be packed as a NuGet and added to the NuGet server. The database could be optimized. I could also add some extra configuration options. I would like to implement an import, export function for SPA json files and also csv files which can be translated by external companys.

I’m open to feedback and would be grateful for tips on how I could improve this. Maybe this could be added to the ASP.NET Core.

Links:

https://github.com/aspnet/Localization

Using DataAnnotations and Localization in ASP.NET Core 1.0 MVC 6

ASP.NET Core 1.0 MVC 6 Localization

https://github.com/aspnet/Tooling/issues/236

http://www.jerriepelser.com/blog/how-aspnet5-determines-culture-info-for-localization

https://github.com/aspnet/Mvc/tree/dev/test/WebSites/LocalizationWebSite

Example of localization middleware for culture in route

http://weblogs.asp.net/jeff/beating-localization-into-submission-on-asp-net-5


Plotly charts using Angular, ASP.NET Core 1.0 and Elasticsearch

$
0
0

This article shows how to use a javascript Plotly Bar Chart in Angular to display data from an ASP.NET Core 1.0 MVC application. The server uses Elasticsearch as persistence and uses it to retrieve the aggregated data and return it to the UI.

Code: https://github.com/damienbod/AngularPlotlyAspNetCore

Setting up the test data in Elasticsearch

The SnakeBites class is used as the model for the data. This class is used to create a mapping in Elasticsearch and also retrieve the data as required. The GeographicalRegion and the Country properties have ElasticsearchString attributes which are used to define the non default mapping in Elasticsearch. The fields are defined as not analyzed so that a terms aggregation or search will work for the whole text with whitespaces. If these are analyzed, Elasticsearch will split the words, which is not what we what in this use case. EleaticsearchCRUD is used to access the Elasticsearch API, NEST could also be used now as the RC1 version runs in dnxcore.

namespace AngularPlotlyAspNetCore.Models
{
    using System;
    using ElasticsearchCRUD.ContextAddDeleteUpdate.CoreTypeAttributes;
    public class SnakeBites
    {
        [ElasticsearchString(Index = StringIndex.not_analyzed)]
        public string GeographicalRegion { get; set; }

        [ElasticsearchString(Index = StringIndex.not_analyzed)]
        public string Country { get; set; }

        public double NumberOfCasesLow { get; set; }
        public double NumberOfCasesHigh { get; set; }
        public double NumberOfDeathsLow { get; set; }
        public double NumberOfDeathsHigh { get; set; }

    }
}

The index mapping is created as follows:

elasticsearchContext.IndexCreate<SnakeBites>();

When the index and type mapping are created, it can be checked in Elasticsearch with the URL
http://localhost:9200/_mapping

http://localhost:9200 is the default Elasticsearch URL.

The mapping is created with non analyzed fields.
plotlyAngularAspNetCoreElasticsearch_01

Data can then be added using the JSON data in the src folder. This needs to be changed in the configuration file if running locally.

List<SnakeBites> data = JsonConvert.DeserializeObject<List<SnakeBites>>(
	File.ReadAllText(_optionsApplicationConfiguration.Value.FilePath)));
long counter = 1;
foreach (var snakeCountry in data)
{
	// create some documents
	counter++;
	elasticsearchContext.AddUpdateDocument(snakeCountry, counter);
}

elasticsearchContext.SaveChanges();

An AddAllData method has been added to the SnakeDataController class so that is is easily to setup to data. This is just wired up using the default DI in the ASP.NET Core 1.0 application.

Creating a Bar Chart using angular-plotly

A plotly chart is added to the angular template using the angular directive plotly.

<div>
    <plotly data="data" layout="layout" options="options"></plotly>
</div>

This directive needs to be added to the main module before you can use this. This is documented on the github angular-plotly (link above).

var mainApp = angular.module("mainApp",
        [
            "ui.router", 
            "plotly"
        ]);

The plotly directive has three parameters. These can be filled with data in an angular controller. The barChartData (data for the bar chart) which is returned from the API in a HTTP GET, is added to the controller using constructor injection. The chart properties are filled with data and the type property is used to specify the chart type. The demo uses the type ‘bar’ for a bar chart. Each chart requires the data in a different format. This is documented on the plotly web page.

(function () {
	'use strict';

	var module = angular.module('mainApp');

	module.controller('RegionBarChartController',
		[
			'$scope',
			'$log',
            'barChartData',
			RegionBarChartController
		]
	);

	function RegionBarChartController($scope, $log, barChartData) {
	    $log.info("RegionBarChartController called");
	    $scope.message = "RegionBarChartController";

	    $scope.barChartData = barChartData;
	    

	    $scope.RegionName = $scope.barChartData.RegionName;

	    $scope.layout = {
	        title: $scope.barChartData.RegionName + ": Number of snake bite deaths" ,
	        height: 500,
	        width: 1200
	    };

	   
	    function getYDatafromDatPoint() {
	        return $scope.barChartData.NumberOfDeathsHighData.Y;
	    }

	    $scope.data = [
          {
              x: $scope.barChartData.X,
              y: getYDatafromDatPoint(),
              name: $scope.barChartData.Datapoint,
              type: 'bar',
              orientation :'v'
          }
	    ];

	    $log.info($scope.data);
	}
})();

The resource data from the API is added to the controller using the resolve from the angular ui-router module. For example the state overview returns a geographicalRegions object from the server API and the object is injected into the constructor of the OverviewController controller.

mainApp.config(["$stateProvider", "$urlRouterProvider",
		function ($stateProvider, $urlRouterProvider) {
            	$urlRouterProvider.otherwise("/overview");

            	$stateProvider
                    .state("overview", {
                        url: "/overview",
                        templateUrl: "/templates/overview.html",
                        controller: "OverviewController",
                        resolve: {

                            SnakeDataService: "SnakeDataService",

                            geographicalRegions: ["SnakeDataService", function (SnakeDataService) {
                                return SnakeDataService.getGeographicalRegions();
                            }]
                        }

                    }).state("regionbarchart", {
                        url: "/regionbarchart/:region",
		                templateUrl: "/templates/regoinbarchart.html",
		                controller: "RegionBarChartController",
		                resolve: {

		                    SnakeDataService: "SnakeDataService",

		                    barChartData: ["SnakeDataService", "$stateParams", function (SnakeDataService, $stateParams) {
		                        return SnakeDataService.getRegionBarChartData($stateParams.region);
		                }]
		        }
		    });           
		}
	]
    );

The SnakeDataService is used to call the server API using the $http service. The different functions use a promise to return the data.

(function () {
	'use strict';

	function SnakeDataService($http, $log) {

	    $log.info("SnakeDataService called");

	    var getGeographicalRegions = function () {
	        $log.info("SnakeDataService GetGeographicalRegions called");
	        return $http.get("/api/SnakeData/GeographicalRegions")
			.then(function (response) {
				return response.data;
			});
		}

		var getRegionBarChartData = function (region) {
		    $log.info("SnakeDataService getRegionBarChartData: " + region);
		    $log.info(region);
		    return $http.get("/api/SnakeData/RegionBarChart/" + region )
			.then(function (response) {
			    return response.data;
			});
		}

		return {
		    getGeographicalRegions: getGeographicalRegions,
		    getRegionBarChartData: getRegionBarChartData
		}
	}

	var module = angular.module('mainApp');

	// this code can be used with uglify
	module.factory("SnakeDataService",
		[
			"$http",
			"$log",
			SnakeDataService
		]
	);

})();

Setting up the ASP.NET Core server to return the data

The SnakeDataController class is the MVC controller class used for the API. This class uses the ISnakeDataRepository to access the data provider.

using System.Collections.Generic;
using AngularPlotlyAspNetCore.Models;
using Microsoft.AspNet.Mvc;

namespace AngularPlotlyAspNetCore.Controllers
{
    [Route("api/[controller]")]
    public class SnakeDataController : Controller
    {
        private ISnakeDataRepository _snakeDataRepository;

        public SnakeDataController(ISnakeDataRepository snakeDataRepository)
        {
            _snakeDataRepository = snakeDataRepository;
        }

        [HttpGet("GeographicalRegions")]
        public List<GeographicalRegion> GetGeographicalRegions()
        {
            return _snakeDataRepository.GetGeographicalRegions();
        }

        [HttpGet("RegionBarChart/{region}")]
        public GeographicalCountries GetBarChartDataForRegion(string region)
        {
            return _snakeDataRepository.GetBarChartDataForRegion(region);
        }

        [HttpGet("AddAllData")]
        public IActionResult AddAllData()
        {
            _snakeDataRepository.AddAllData();
            return Ok();
        }
    }
}

The SnakeDataRepository class implements the ISnakeDataRepository interface. This is then defined in the Startup class using the ASP.NET Core 1.0 default IoC.

public void ConfigureServices(IServiceCollection services)
{
	// Add framework services.
	services.AddMvc();

	services.AddScoped<ISnakeDataRepository, SnakeDataRepository>();
}

The SnakeDataRepository implements the Elasticsearch API. The GetGeographicalRegions implements a terms bucket aggregation per region and then a sum metric aggregation on the required properties. The result is then returned in a list of GeographicalRegion objects.

public List<GeographicalRegion> GetGeographicalRegions()
{
	List<GeographicalRegion> geographicalRegions = new List<GeographicalRegion>();
	var search = new Search
	{
		Aggs = new List<IAggs>
		{
			new TermsBucketAggregation("getgeographicalregions", "geographicalregion")
			{
				Aggs = new List<IAggs>
				{
					new SumMetricAggregation("countCases", "numberofcaseshigh"),
					new SumMetricAggregation("countDeaths", "numberofdeathshigh")
				}
			}
		}
	};

	using (var context = new ElasticsearchContext(_connectionString, _elasticsearchMappingResolver))
	{
		var items = context.Search<SnakeBites>(
			search,
			new SearchUrlParameters
			{
				SeachType = SeachType.count
			});

		try
		{
			var aggResult = items.PayloadResult.Aggregations.GetComplexValue<TermsBucketAggregationsResult>("getgeographicalregions");

			foreach (var bucket in aggResult.Buckets)
			{
				var cases = Math.Round(bucket.GetSingleMetricSubAggregationValue<double>("countCases"), 2);
				var deaths = Math.Round(bucket.GetSingleMetricSubAggregationValue<double>("countDeaths"), 2);
				geographicalRegions.Add(
					new GeographicalRegion {
						Countries = bucket.DocCount,
						Name = bucket.Key.ToString(),
						NumberOfCasesHigh = cases,
						NumberOfDeathsHigh = deaths,
						DangerHigh =  (deaths > 1000)
					});


			}
		}
		catch (Exception ex)
		{
		  Console.WriteLine(ex.Message);
		}
	}
		   
	return geographicalRegions;
} 

The GetBarChartDataForRegion method just searches for the data using a simple match query on the geographicalregion field. The size is increased to a 100, as the default Hits from Elasticsearch is set to 10. The result is returned as a GeographicalCountries object which is used for the bar charts.

public GeographicalCountries GetBarChartDataForRegion(string region)
{
	GeographicalCountries result = new GeographicalCountries { RegionName = region};

	var search = new Search
	{
		Query = new Query(new MatchQuery("geographicalregion", region)),
		Size= 100
	};

	using (var context = new ElasticsearchContext(_connectionString, _elasticsearchMappingResolver))
	{
		var items = context.Search<SnakeBites>(search);
		
		result.NumberOfCasesHighData = new BarTrace { Y = new List<double>()};
		result.NumberOfCasesLowData = new BarTrace {Y = new List<double>() };
		result.NumberOfDeathsHighData = new BarTrace {  Y = new List<double>() };
		result.NumberOfDeathsLowData = new BarTrace {  Y = new List<double>() };
		result.X = new List<string>();

		foreach (var item in items.PayloadResult.Hits.HitsResult)
		{
			result.NumberOfCasesHighData.Y.Add(item.Source.NumberOfCasesHigh);
			result.NumberOfCasesLowData.Y.Add(item.Source.NumberOfCasesLow);
			result.NumberOfDeathsHighData.Y.Add(item.Source.NumberOfDeathsHigh);
			result.NumberOfDeathsLowData.Y.Add(item.Source.NumberOfDeathsLow);

			result.X.Add(item.Source.Country);
		}
	}

	return result;
}

Running

When the application is run, the data is displayed in a pro region aggregated form.
plotlyAngularAspNetCoreElasticsearch_02

When the region is clicked, the Plotly Bar chart is displayed showing the difference pro country in that region.
plotlyAngularAspNetCoreElasticsearch_03

Notes

The demo data is from the website: International Society on Toxinology – Global Snakebite Initiative.

Before running the test code, Elasticsearch and ASP.NET Core 1.0 RC1 need to be installed.

Links:

https://plot.ly/javascript/

https://github.com/alonho/angular-plotly

https://www.elastic.co/products/elasticsearch


Authorization Policies and Data Protection with IdentityServer4 in ASP.NET Core

$
0
0

This article shows how authorization policies can be used together with IdentityServer4. The policies are configured on the resource server and the ASP.NET Core IdentityServer4 configures the user claims to match these. The resource server is also setup to encrypt a ‘Description’ field in the SQLite database, so it cannot be read by opening the SQLite database directly.

The demo uses and extends the existing code from this previous article:
OAuth2 Implicit Flow with Angular and ASP.NET Core 1.0 IdentityServer4

Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow

Extending the Resource Server with Policies

An authorization policy can be added to the MVC application in the Startup class ConfigureServices method. The policy can be added globally using the filters or individually using attributes on a class or method. To add a global authorization policy, the AuthorizationPolicyBuilder helper method can be used to create the policy. The claimType parameter in the RequireClaim method must match a claim supplied in the token.

Then the policy can be added using the AuthorizeFilter in the AddMVC extension.

var guestPolicy = new AuthorizationPolicyBuilder()
	.RequireAuthenticatedUser()
	.RequireClaim("scope", "dataEventRecords")
	.Build();

services.AddMvc(options =>
{
   options.Filters.Add(new AuthorizeFilter(guestPolicy));
});

To create policies which can be used individually, the AddAuthorization extension method can be used. Again the claimType parameter in the RequireClaim method must match a claim supplied in the token.


services.AddAuthorization(options =>
{
	options.AddPolicy("dataEventRecordsAdmin", policyAdmin =>
	{
		policyAdmin.RequireClaim("role", "dataEventRecords.admin");
	});
	options.AddPolicy("dataEventRecordsUser", policyUser =>
	{
		policyUser.RequireClaim("role",  "dataEventRecords.user");
	});

});

To use and authenticate the token from IdentityServer4, the UseJwtBearerAuthentication extension method can be used in the Configure method in the Startup class of the MVC application.

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();

app.UseJwtBearerAuthentication(options =>
{
	options.Authority = "https://localhost:44345";
	options.Audience = "https://localhost:44345/resources";
	options.AutomaticAuthenticate = true;
	options.AutomaticChallenge = true;
});

The authorization policies can then be applied in the controller using authorization filters with the policy name as a parameter. Maybe it’s not a good idea to define each method with a different policy as this might be hard to maintain over time, maybe per controller might be more appropriate, all depends on your requirements. It pays off to define your security strategy before implementing it.


[Authorize]
[Route("api/[controller]")]
public class DataEventRecordsController : Controller
{
	[Authorize("dataEventRecordsUser")]
	[HttpGet]
	public IEnumerable<DataEventRecord> Get()
	{
		return _dataEventRecordRepository.GetAll();
	}

	[Authorize("dataEventRecordsAdmin")]
	[HttpGet("{id}")]
	public DataEventRecord Get(long id)
	{
		return _dataEventRecordRepository.Get(id);
	}

Adding the User Claims and Client Scopes in IdentityServer4

The resource server has been setup to check for claim types of ‘role’ with the value of ‘dataEventRecords.user’ or ‘dataEventRecords.admin’. The application is also setup to check for claims type ‘scope’ with the value of ‘dataEventRecords’. IdentityServer4 needs to be configured for this. The scope is configured for the client in the IdentityServerAspNet5 project.

new Scope
{
	Name = "dataEventRecords",
	DisplayName = "Data Event Records Scope",
	Type = ScopeType.Resource,

	Claims = new List<ScopeClaim>
	{
		new ScopeClaim("role"),
		new ScopeClaim("dataEventRecords")
	}
},

Then users are also configured and the appropriate role and scope claims for each user. (And some others which aren’t required for the angular client.) Two users are configured, damienboduser and damienbodadmin. The damienboduser has not the ‘dataEventRecords.admin’ role claim. This means that the user cannot create or update a user, only see the list. (Configured in the MVC Controller of the resource server)

new InMemoryUser{Subject = "48421157", Username = "damienbodadmin", Password = "damienbod",
  Claims = new Claim[]
  {
	new Claim(Constants.ClaimTypes.Name, "damienbodadmin"),
	new Claim(Constants.ClaimTypes.GivenName, "damienbodadmin"),
	new Claim(Constants.ClaimTypes.Email, "damien_bod@hotmail.com"),
	new Claim(Constants.ClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
	new Claim(Constants.ClaimTypes.Role, "admin"),
	new Claim(Constants.ClaimTypes.Role, "dataEventRecords.admin"),
	new Claim(Constants.ClaimTypes.Role, "dataEventRecords.user"),
	new Claim(Constants.ClaimTypes.Role, "dataEventRecords")
  }
},
new InMemoryUser{Subject = "48421158", Username = "damienboduser", Password = "damienbod",
  Claims = new Claim[]
  {
	new Claim(Constants.ClaimTypes.Name, "damienboduser"),
	new Claim(Constants.ClaimTypes.GivenName, "damienboduser"),
	new Claim(Constants.ClaimTypes.Email, "damien_bod@hotmail.com"),
	new Claim(Constants.ClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
	new Claim(Constants.ClaimTypes.Role, "user"),
	new Claim(Constants.ClaimTypes.Role, "dataEventRecords.user"),
	new Claim(Constants.ClaimTypes.Role, "dataEventRecords")
  }
}

The security for the client application is configured to allow the different scopes which can be used. If the client requests a scope which isn’t configured here, the client application request will be rejected.

new Client
{
	ClientName = "angularclient",
	ClientId = "angularclient",
	Flow = Flows.Implicit,
	RedirectUris = new List<string>
	{
		"https://localhost:44347/identitytestclient.html",
		"https://localhost:44347/authorized"

	},
	PostLogoutRedirectUris = new List<string>
	{
		"https://localhost:44347/identitytestclient.html",
		"https://localhost:44347/authorized"
	},
	AllowedScopes = new List<string>
	{
		"openid",
		"email",
		"profile",
		"dataEventRecords",
		"aReallyCoolScope",
		"role"
	}
},

If the user damienboduser sends a HTTP GET request for the single item, the resource server returns a 403 with no body. If the AutomaticChallenge option in the UseJwtBearerAuthentication is false, this will be returned as a 401.

dataprotectionAspNet5IdentityServerAngularImplicitFlow_02

Using Data Protection to encrypt the SQLite data

It is really easy to encrypt your data using the Data Protection library from ASP.NET Core. To use it in an MVC application, just add it in the ConfigureServices method using the DataProtection extension methods. There are a few different ways to configure this and it is well documented here.

This example uses the file system with a self signed cert as data protection configuration. This will work for long lived encryption and is easy to restore. The ProtectKeysWithCertificate method does not work in dnxcore at present, but hopefully this will be implemented in RC2. Thanks to Barry Dorrans ‏@blowdart for all his help.

public void ConfigureServices(IServiceCollection services)
{
 var folderForKeyStore = Configuration["Production:KeyStoreFolderWhichIsBacked"];
 var cert = new X509Certificate2(Path.Combine(_environment.ApplicationBasePath, "damienbodserver.pfx"), "");

 services.AddDataProtection();
 services.ConfigureDataProtection(configure =>
 {
	configure.SetApplicationName("AspNet5IdentityServerAngularImplicitFlow");
	configure.ProtectKeysWithCertificate(cert);
	// This folder needs to be backed up.
	configure.PersistKeysToFileSystem(new DirectoryInfo(folderForKeyStore));
	
 });

Now the IDataProtectionProvider interface can be injected in your class using constructor injection. You can then create a protector using the CreateProtector method. Care should be taken on how you define the string in this method. See the documentation for the recommendations.

private readonly DataEventRecordContext _context;
private readonly ILogger _logger;
private IDataProtector _protector;

public DataEventRecordRepository(IDataProtectionProvider provider, 
                 DataEventRecordContext context, 
                 ILoggerFactory loggerFactory)
{
	_context = context;
	_logger = loggerFactory.CreateLogger("IDataEventRecordResporitory");
	_protector = provider.CreateProtector("DataEventRecordRepository.v1");
}

Now the protector IDataProtectionProvider can be used to Protect and also Unprotect the data. In this example all descriptions which are saved to the SQLite database are encrypted using the default data protection settings.

private void protectDescription(DataEventRecord dataEventRecord)
{
	var protectedData = _protector.Protect(dataEventRecord.Description);
	dataEventRecord.Description = protectedData;
}

private void unprotectDescription(DataEventRecord dataEventRecord)
{
	var unprotectedData = _protector.Unprotect(dataEventRecord.Description);
	dataEventRecord.Description = unprotectedData;
}

When the SQLite database is opened, you can see that all description fields are encrypted.

dataprotectionAspNet5IdentityServerAngularImplicitFlow_01png

Links

NDC London 2016 Wrap-up

Announcing IdentityServer for ASP.NET 5 and .NET Core

https://github.com/IdentityServer/IdentityServer4

https://github.com/IdentityServer/IdentityServer4.Samples

https://github.com/aspnet/DataProtection

ASP.NET Core 1 – Authorization using Policies

http://docs.asp.net/en/latest/security/data-protection/index.html

OAuth2 Implicit Flow with Angular and ASP.NET Core 1.0 IdentityServer4

The State of Security in ASP.NET 5 and MVC 6: OAuth 2.0, OpenID Connect and IdentityServer

http://capesean.co.za/blog/asp-net-5-jwt-tokens/

https://github.com/tjoudeh/AngularJSAuthentication


Viewing all 354 articles
Browse latest View live