Pluralsight Course Published – Designing an Intelligent Edge in Microsoft Azure

Designing an Intelligent Edge in Microsoft Azure was just published on Pluralsight! Check it out. Here is a synopsis of what’s in it:

This course targets software developers that are looking to integrate AI solutions in edge scenarios ranging from an edge data center down to secure microcontrollers. This course will showcase how to design solutions using Microsoft Azure.

Cloud computing has moved more and more out of the cloud and onto the edge. In this course, Designing an Intelligent Edge in Microsoft Azure, you will learn foundational knowledge of edge computing, its intersection with AI, and how to utilize both with Microsoft Azure. First, you will learn the concepts of edge computing. Next, you will discover how to create an edge solution utilizing Azure Stack, Azure Data Box Edge, and Azure IoT Edge. Finally, you will explore how to utilize off the shelf AI and build your own for Azure IoT Edge. When you are finished with this course, you will have the skills and knowledge of AI on the edge needed to architect your next edge solution. Software required: Microsoft Azure, .NET

 

Communicating between Python and .NET Core with Boost Interprocess

To see if I could, I put together a cross communication library for .Net Core and Python applications using Boost.Interprocess, Boost.Python, and Boost.Signals2. The goal was simple, expose the same interface for cross communication to C# and Python. The approach taken was to use the condition example and edit it to expose to the different languages.

Shared Definitions

First I need to create the objects to make the interface. There are four files making up these objects:

  • shm_remove.hpp – just a lifecycle object to clear the shared buffer when it is destructed
  • TraceQueue.hpp – The shared memory object
  • SharedMemoryConsumer.hpp – The subscriber to the shared memory data
  • SharedMemoryProducer.hpp – The publisher for the shared memory data
template<size_t BufferSize>
class SharedMemoryConsumer
{
public:
explicit SharedMemoryConsumer(const std::string& memory_name)
: name_length_(memory_name.size()),
shm_(boost::interprocess::open_or_create,memory_name.c_str(),boost::interprocess::read_write)
{
shm_.truncate(sizeof(TraceQueue<BufferSize>));
region_ = boost::interprocess::mapped_region(shm_,boost::interprocess::read_write);
//Get the address of the mapped region
void * addr = region_.get_address();
//Obtain a pointer to the shared structure
data_ = static_cast<TraceQueue<BufferSize>*>(addr);
if (data_->name_length != name_length_)
{
data_ = new (addr)TraceQueue<BufferSize>;
data_->name_length = name_length_;
}
}
~SharedMemoryConsumer()
{
keep_listening_ = false;
background_listener_.join();
}
void start()
{
keep_listening_ = true;
background_listener_ = std::thread([=](){
do {
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> lock(data_->mutex);
if (!data_->message_in) {
data_->cond_empty.wait(lock);
}
sig_(data_->message);
//Notify the other process that the buffer is empty
data_->message_in = false;
data_->cond_full.notify_one();
} while (!data_->closing_stream && keep_listening_);
});
}
void set_callable(const std::function<void(const InterprocessMessage<BufferSize>& memory_block)>& callback)
{
conn_ = sig_.connect([=](const InterprocessMessage<BufferSize>& message)
{
callback(message);
});
}
void wait()
{
background_listener_.join();
}
private:
int name_length_;
boost::signals2::signal<void(const InterprocessMessage<BufferSize>&)> sig_;
boost::signals2::connection conn_;
boost::interprocess::shared_memory_object shm_;
boost::interprocess::mapped_region region_;
std::thread background_listener_;
bool keep_listening_;
TraceQueue<BufferSize> * data_;
};

template <size_t BufferSize>
class SharedMemoryProducer
{
public:
explicit SharedMemoryProducer(const std::string& memory_name)
: name_length_(memory_name.size())
,remove_guard_(memory_name)
, shm_(boost::interprocess::open_or_create,memory_name.c_str(),boost::interprocess::read_write)
{
shm_.truncate(sizeof(TraceQueue<BufferSize>));
region_ = boost::interprocess::mapped_region(shm_,boost::interprocess::read_write);
//Get the address of the mapped region
void * addr = region_.get_address();
//Construct the shared structure in memory
data_ = static_cast<TraceQueue<BufferSize>*>(addr);
if (data_->name_length != name_length_)
{
data_ = new (addr)TraceQueue<BufferSize>;
data_->name_length = name_length_;
}
}
~SharedMemoryProducer()
{
data_->name_length = 0;
data_->closing_stream = true;
}
void write_data(const std::string& buffer)
{
if (buffer.size() > BufferSize)
{
throw std::invalid_argument("Message size is larger than buffer size");
}
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> lock(data_->mutex);
if(data_->message_in){
data_->cond_full.wait(lock);
}
memcpy(data_->message.message,buffer.data(),buffer.size());
data_->message.message_length = buffer.size();
//Notify to the other process that there is a message
data_->cond_empty.notify_one();
//Mark message buffer as full
data_->message_in = true;
}
private:
int name_length_;
shm_remove remove_guard_;
boost::interprocess::shared_memory_object shm_;
boost::interprocess::mapped_region region_;
TraceQueue<BufferSize> * data_;
};

#include <boost/interprocess/shared_memory_object.hpp>
struct shm_remove
{
public:
explicit shm_remove(const std::string& shm_name)
: shm_name_(shm_name)
{
// boost::interprocess::shared_memory_object::remove(shm_name.c_str());
}
~shm_remove(){ boost::interprocess::shared_memory_object::remove(shm_name_.c_str()); }
private:
std::string shm_name_;
};

view raw
shm_remove.hpp
hosted with ❤ by GitHub

template <size_t MESSAGE_SIZE>
struct InterprocessMessage
{
//Items to fill
char message[MESSAGE_SIZE];
//Message size
size_t message_length;
std::string to_string() const
{
return std::string(message,message_length);
}
};
template <size_t MESSAGE_SIZE>
struct TraceQueue
{
TraceQueue()
: message_in(false)
{}
//Mutex to protect access to the queue
boost::interprocess::interprocess_mutex mutex;
//Condition to wait when the queue is empty
boost::interprocess::interprocess_condition cond_empty;
//Condition to wait when the queue is full
boost::interprocess::interprocess_condition cond_full;
//Message
InterprocessMessage<MESSAGE_SIZE> message;
//Is there any message
bool message_in;
//Notify closing shop
bool closing_stream = false;
//Check to see if memory has been created
int name_length;
};

view raw
TraceQueue.hpp
hosted with ❤ by GitHub

These objects comprise the core interface of the shared memory provider. Now, the memory providers need to be exposed to multiple languages. There are different ways to do this and I decided to do it by hand. I should point out SWIG is my usual approach to this task, however, in this instance it seemed easy enough to do it by hand.

Boost Python

To expose the python code, I needed to create a few classes to expose the interface definitions to Boost.Python. The two classes are:

  • PythonSharedMemoryConsumer.hpp – The python interface for the SharedMemoryConsumer
  • PythonModule.cpp  – The file that exposes the module to python
BOOST_PYTHON_MODULE(ScryUnlimitedCommunicator)
{
Py_Initialize();
class_<SeralizedPythonSharedMemoryConsumer, boost::noncopyable>("SeralizedSharedMemoryConsumer",init<std::string>())
.def("set_callable", &SeralizedPythonSharedMemoryConsumer::set_callable)
.def("start",&SeralizedPythonSharedMemoryConsumer::start)
.def("wait",&SeralizedPythonSharedMemoryConsumer::wait);
class_<ImagePythonSharedMemoryConsumer, boost::noncopyable>("ImageSharedMemoryConsumer",init<std::string>())
.def("set_callable", &ImagePythonSharedMemoryConsumer::set_callable)
.def("start",&ImagePythonSharedMemoryConsumer::start)
.def("wait",&ImagePythonSharedMemoryConsumer::wait);
class_<SeralizedSharedMemoryProducer, boost::noncopyable>("SeralizedSharedMemoryProducer",init<std::string>())
.def("write_data", &SeralizedSharedMemoryProducer::write_data);
class_<ImageSharedMemoryProducer, boost::noncopyable>("ImageSharedMemoryProducer",init<std::string>())
.def("write_data", &ImageSharedMemoryProducer::write_data);
}

view raw
PythonModule.cpp
hosted with ❤ by GitHub

template <size_t BufferSize>
class PythonSharedMemoryConsumer
{
public:
explicit PythonSharedMemoryConsumer(const std::string& memory_name)
: sm_(memory_name) {}
void set_callable(boost::python::object callback)
{
if (!PyCallable_Check(callback.ptr()))
{
PyErr_SetString(PyExc_TypeError, "Callback must be a callable object");
boost::python::throw_error_already_set();
}
sm_.set_callable([=](const InterprocessMessage<BufferSize>& buffer)
{
try
{
callback(boost::python::object(
boost::python::handle<>(
PyBytes_FromStringAndSize(buffer.message,buffer.message_length))));
//callback(buffer.to_string());
}
catch (boost::python::error_already_set)
{
PyErr_Print();
}
});
}
void start()
{
sm_.start();
}
void wait()
{
sm_.wait();
}
private:
ScryUnlimited::SharedMemory::SharedMemoryConsumer<BufferSize> sm_;
};

These two classes combine to expose the files to python and can be used in a python script by just importing the shared library.

.NET Core

With the python portion complete, I needed to expose the shared memory objects to CSharp. This is easy enough to do by hand if you expose the classes to be used by PInvoke. To accomplish this, I only needed three files:

  • NetCoreSharedMemoryProducer.hpp – The .NET Core version of the publisher
  • NetCoreSharedMemoryConsumer.hpp – The .NET Core version of the consumer
  • NetCoreModule.cpp – The source file exposing the interfaces for PInvoke

 

extern "C" ImageNetCoreSharedMemoryConsumer* GetImageConsumer(char * name)
{
return new ImageNetCoreSharedMemoryConsumer(std::string(name));
}
extern "C" void DeleteImageNetCoreSharedMemoryConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void ImageConsumerSetCallback(ImageNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartImageConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void ImageWait(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" SeralizedNetCoreSharedMemoryConsumer * GetSerializedConsumer(const char * name)
{
return new SeralizedNetCoreSharedMemoryConsumer(name);
}
extern "C" void DeleteSeralizedNetCoreSharedMemoryConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void SeralizedConsumerSetCallback(SeralizedNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartSeralizedConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void SeralizedWait(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" ImageNetCoreSharedMemoryProducer * GetImageProducer(const char * name)
{
return new ImageNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteImageProducer(ImageNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendImageData(ImageNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}
extern "C" SeralizedNetCoreSharedMemoryProducer * GetSeralizedProducer(const char * name)
{
return new SeralizedNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteSeralizedProducer(SeralizedNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendSeralizedData(SeralizedNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}

view raw
NetCoreModule.cpp
hosted with ❤ by GitHub

template <size_t BufferSize>
class NetCoreSharedMemoryConsumer
{
public:
explicit NetCoreSharedMemoryConsumer(const std::string& name)
: consumer_(name){}
void set_callback(void (*callback_method)(const unsigned char *,size_t))
{
consumer_.set_callable([=](const InterprocessMessage<BufferSize>& message)
{
callback_method(reinterpret_cast<const unsigned char *>(message.message),message.message_length);
});
}
void start()
{
consumer_.start();
}
void wait()
{
consumer_.wait();
}
private:
SharedMemoryConsumer<BufferSize> consumer_;
};

template <size_t BufferSize>
class NetCoreSharedMemoryProducer
{
public:
explicit NetCoreSharedMemoryProducer(const std::string& name)
: producer_(name){}
void write_data(const std::string& data)
{
producer_.write_data(data);
}
private:
SharedMemoryProducer<BufferSize> producer_;
};

extern "C" ImageNetCoreSharedMemoryConsumer* GetImageConsumer(char * name)
{
return new ImageNetCoreSharedMemoryConsumer(std::string(name));
}
extern "C" void DeleteImageNetCoreSharedMemoryConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void ImageConsumerSetCallback(ImageNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartImageConsumer(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void ImageWait(ImageNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" SeralizedNetCoreSharedMemoryConsumer * GetSerializedConsumer(const char * name)
{
return new SeralizedNetCoreSharedMemoryConsumer(name);
}
extern "C" void DeleteSeralizedNetCoreSharedMemoryConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
delete consumer;
}
extern "C" void SeralizedConsumerSetCallback(SeralizedNetCoreSharedMemoryConsumer* consumer,
void(*callback)(const unsigned char *,size_t))
{
consumer->set_callback(callback);
}
extern "C" void StartSeralizedConsumer(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->start();
}
extern "C" void SeralizedWait(SeralizedNetCoreSharedMemoryConsumer* consumer)
{
consumer->wait();
}
extern "C" ImageNetCoreSharedMemoryProducer * GetImageProducer(const char * name)
{
return new ImageNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteImageProducer(ImageNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendImageData(ImageNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}
extern "C" SeralizedNetCoreSharedMemoryProducer * GetSeralizedProducer(const char * name)
{
return new SeralizedNetCoreSharedMemoryProducer(name);
}
extern "C" void DeleteSeralizedProducer(SeralizedNetCoreSharedMemoryProducer * producer)
{
delete producer;
}
extern "C" void SendSeralizedData(SeralizedNetCoreSharedMemoryProducer * producer,void * data_ptr,size_t size)
{
producer->write_data(std::string(static_cast<char*>(data_ptr),size));
}

view raw
NetCoreModule.cpp
hosted with ❤ by GitHub

template <size_t BufferSize>
class NetCoreSharedMemoryConsumer
{
public:
explicit NetCoreSharedMemoryConsumer(const std::string& name)
: consumer_(name){}
void set_callback(void (*callback_method)(const unsigned char *,size_t))
{
consumer_.set_callable([=](const InterprocessMessage<BufferSize>& message)
{
callback_method(reinterpret_cast<const unsigned char *>(message.message),message.message_length);
});
}
void start()
{
consumer_.start();
}
void wait()
{
consumer_.wait();
}
private:
SharedMemoryConsumer<BufferSize> consumer_;
};

template <size_t BufferSize>
class NetCoreSharedMemoryProducer
{
public:
explicit NetCoreSharedMemoryProducer(const std::string& name)
: producer_(name){}
void write_data(const std::string& data)
{
producer_.write_data(data);
}
private:
SharedMemoryProducer<BufferSize> producer_;
};

Now we need to call that code from C# using PInvoke Interop

class ImageSharedMemoryConsumer
: ISharedMemoryConsumer
{
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern IntPtr GetImageConsumer(string name);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void DeleteImageNetCoreSharedMemoryConsumer(IntPtr instance);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void ImageConsumerSetCallback(IntPtr instance, IntPtr callback);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void StartImageConsumer(IntPtr instance);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void SeralizedWait(IntPtr instance);
private readonly IntPtr _nativePointer;
private IntPtr _callbackPtr;
private GCHandle _callbackHandle;
public ImageSharedMemoryConsumer(string name)
{
_nativePointer = GetImageConsumer(name);
}
~ImageSharedMemoryConsumer()
{
DeleteImageNetCoreSharedMemoryConsumer(_nativePointer);
}
public long Size()
{
return 3000000;
}
public void SetCallback(HandleMessage handler)
{
void Callback(IntPtr x, int y)
{
byte[] managedArray = new byte[y];
Marshal.Copy(x, managedArray, 0, y);
handler(managedArray);
}
_callbackPtr = Marshal.GetFunctionPointerForDelegate(new InternalHandleMessage(Callback));
_callbackHandle = GCHandle.Alloc((Action<IntPtr, int>) Callback);
ImageConsumerSetCallback(_nativePointer,_callbackPtr);
}
public void Start()
{
StartImageConsumer(_nativePointer);
}
public void Wait()
{
SeralizedWait(_nativePointer);
}
}

class ImageSharedMemoryProducer
: ISharedMemoryProducer
{
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern IntPtr GetImageProducer(string name);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void DeleteImageProducer(IntPtr handle);
[DllImport("ScryUnlimitedCommunicator.so")]
private static extern void SendImageData(IntPtr handle, byte[] data,long length);
private IntPtr _nativeHandle;
public ImageSharedMemoryProducer(string name)
{
_nativeHandle = GetImageProducer(name);
}
~ImageSharedMemoryProducer()
{
DeleteImageProducer(_nativeHandle);
}
public long Size()
{
return 3000000;
}
public void Write(byte[] message)
{
SendImageData(_nativeHandle,message,message.LongLength);
}
}

using System;
public delegate void HandleMessage(byte[] message);
internal delegate void InternalHandleMessage(IntPtr array, int length);
public interface ISharedMemoryConsumer
{
long Size();
void SetCallback(HandleMessage handler);
void Start();
void Wait();
}

public interface ISharedMemoryProducer
{
long Size();
void Write(byte[] message);
}

 

Using Angular Kendo Grid with Elastic Search and ASP.NET Core

There was a need for using a Kendo Grid in an Angular 5 website where the backing store for the data was Elastic Search. Utilizing the filtering on local data was simple enough but for the needs of filtering there needed to be server side integration. The server was running ASP.NET Core.

To get started create a view and view model for Angular to expose the grid.

import { Component } from '@angular/core';
import { process, State } from '@progress/kendo-data-query';
import { sampleProducts } from './products';
import {
GridComponent,
GridDataResult,
DataStateChangeEvent
} from '@progress/kendo-angular-grid';
@Component({
selector: 'my-app',
template: `
<kendo-grid
[data]="gridData"
[pageSize]="state.take"
[skip]="state.skip"
[sort]="state.sort"
[filter]="state.filter"
[sortable]="true"
[pageable]="true"
[filterable]="true"
(dataStateChange)="dataStateChange($event)">
<kendo-grid-column field="ProductID" title="ID" width="40" [filterable]="false">
</kendo-grid-column>
<kendo-grid-column field="ProductName" title="Product Name">
</kendo-grid-column>
<kendo-grid-column field="FirstOrderedOn" title="First Ordered On" width="240" filter="date" format="{0:d}">
</kendo-grid-column>
<kendo-grid-column field="UnitPrice" title="Unit Price" width="180" filter="numeric" format="{0:c}">
</kendo-grid-column>
<kendo-grid-column field="Discontinued" width="120" filter="boolean">
<ng-template kendoGridCellTemplate let-dataItem>
<input type="checkbox" [checked]="dataItem.Discontinued" disabled/>
</ng-template>
</kendo-grid-column>
</kendo-grid>
`
})
export class AppComponent {
public state: State = {
skip: 0,
take: 5,
// Initial filter descriptor
filter: {
logic: 'and',
filters: [{ field: 'ProductName', operator: 'contains', value: 'Chef' }]
}
};
public gridData: GridDataResult = process(sampleProducts, this.state);
public dataStateChange(state: DataStateChangeEvent): void {
this.state = state;
this.gridData = process(sampleProducts, this.state);
}
}

view raw
app.component.ts
hosted with ❤ by GitHub

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { GridModule } from '@progress/kendo-angular-grid';
import { AppComponent } from './app.component';
@NgModule({
bootstrap: [
AppComponent
],
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
GridModule
]
})
export class AppModule { }

view raw
app.module.ts
hosted with ❤ by GitHub

import { AppModule } from './app.module';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

view raw
main.ts
hosted with ❤ by GitHub

To wire the view and view model to the server side data, there needs to be an Angular HTTP service and an ASP.NET Core Controller. The controller needs to be able to accept the filter and paging options of the grid as the user changes them and react to them server side. To accomplish this, some objects need to be created to handle the request:

First, the filter object, which is changed whenever a new filter is selected or is cleared; must be mapped to a C# object that can be serialized. The structure of the Kendo Grid filter is as such:

filter: {
      logic: 'and',
      filters: [{ field: 'ProductName', operator: 'contains', value: 'Chef' }]
}

To make that object transportable to C#, lets create a POCO:

public class CompositeFilterDescriptor
{
[JsonProperty("logic")]
public string Logic { get; set; }
[JsonProperty("filters")]
public ICollection<CompositeFilterDescriptor> Filters { get; set; }
[JsonProperty("field")]
public string Field { get; set; }
[JsonProperty("operator")]
public string Operator { get; set; }
[JsonProperty("value")]
public object Value { get; set; }
[JsonProperty("ignoreCase")]
public bool? IgnoreCase { get; set; }
}

public class Page
{
public int Skip { get; set; }
public int Take { get; set; }
}

view raw
Page.cs
hosted with ❤ by GitHub

public class RequestDto
{
public CompositeFilterDescriptor Filters { get; set; }
public Page Page { get; set; }
}

view raw
RequestDto.cs
hosted with ❤ by GitHub

Now lets create an ASP.NET Core controller endpoint for our Filter.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Nest;
public class GridController : Controller
{
private readonly ElasticClient _elasticClient;
public GridController()
{
//Initalize ElasticClient
}
[HttpPost]
public async Task<IActionResult> PostRequest([FromBody]RequestDto request)
{
CompositeFilterMapper compositeFilterMapper = new CompositeFilterMapper();
//Grab a page from elastic
selector = search =>
search.Index("your-index")
.From(request.Page.Skip)
.Size(request.Page.Take)
.Query(q =>
compositeFilterMapper.GetQueryContainer(q));
var searchResponse = _elasticClient.Search(selector);
return Ok(new ResponseDto
{
Results = searchResponse.Documents,
Count = searchResponse.Total
});
}
}

view raw
GridController.cs
hosted with ❤ by GitHub

public class ResponseDto<T>
{
public IEnumerable<T> Results { get; set; }
public long Count { get; set; }
}

view raw
ResponseDto.cs
hosted with ❤ by GitHub

The only thing missing now is the query to work is the CompositeFilterMapper.

class CompositeFilterMapper<T>
{
private readonly CompositeFilterDescriptor _compositeFilterDescriptor;=
public CompositeFilterMapper(CompositeFilterDescriptor compositeFilterDescriptor)
{
_compositeFilterDescriptor = compositeFilterDescriptor;
}
public QueryContainer GetQueryContainer(QueryContainerDescriptor<ElasticDataUpload<T>> query)
{
return ApplyQuery(new[] {_compositeFilterDescriptor}, _compositeFilterDescriptor.Logic, query);
}
private QueryContainerDescriptor<T> ApplyQuery(
IEnumerable<CompositeFilterDescriptor> filterFilters, string filterOperator,
QueryContainerDescriptor<T> query)
{
List<QueryContainerDescriptor<T>> innerQueries =
new List<QueryContainerDescriptor<T>>();
foreach (var filter in filterFilters)
{
if (filter.Filters != null && filter.Filters.Any())
{
innerQueries.Add(ApplyQuery(filter.Filters, filter.Logic, query));
}
else
{
innerQueries.Add((QueryContainerDescriptor<T>)CreateQuery(filter.Field, filter.Operator, filter.IgnoreCase, filter.Logic, filter.Value, query));
}
}
if (filterOperator == "and")
{
return (QueryContainerDescriptor<T>)query.Bool(b => b.Must(innerQueries.Cast<QueryContainer>().ToArray()));
}
else if (filterOperator == "or")
{
return (QueryContainerDescriptor<T>) query.Bool(b => b.Should(innerQueries.Cast<QueryContainer>().ToArray()));
}
else
{
throw new ArgumentOutOfRangeException();
}
}
private QueryContainer CreateQuery(string filterField,
string filterOperator,bool? filterIgnoreCase, string filterLogic, object value,
QueryContainerDescriptor<T> query)
{
var type = MapToType(filterField);
var propertyExpression = MapToProperty(filterField);
return CreateQuery(query,type, propertyExpression, filterOperator, value);
}
private QueryContainer CreateQuery(QueryContainerDescriptor<T> query,
Type fieldType, Expression<Func<T, object>> propertyExpression,
string filterOperator, object value)
{
if (fieldType == typeof(String))
{
return CreateStringFilter(query,propertyExpression, filterOperator, value);
}
else if (fieldType == typeof(DateTime))
{
return CreateDateTimeFilter(query, propertyExpression, filterOperator, value);
}
else
{
throw new ArgumentOutOfRangeException();
}
}
private QueryContainer CreateDateTimeFilter(QueryContainerDescriptor<ElasticDataUpload<SystemLog>> query,
Expression<Func<ElasticDataUpload<SystemLog>, object>> propertyExpression, string filterOperator,
object value)
{
switch (filterOperator)
{
case "eq":
return query.DateRange(d => d.Field(propertyExpression)
.GreaterThanOrEquals(DateMath.Anchored((DateTime)value))
.LessThanOrEquals(DateMath.Anchored((DateTime)value)));
case "neq":
return query.DateRange(d => d.Field(propertyExpression)
.GreaterThanOrEquals(DateMath.Anchored((DateTime)value))
.LessThanOrEquals(DateMath.Anchored((DateTime)value)));
case "isnull":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "isnotnull":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "lt":
return query.DateRange(d => d.Field(propertyExpression).LessThan(DateMath.Anchored((DateTime)value)));
case "lte":
return query.DateRange(d => d.Field(propertyExpression).LessThanOrEquals(DateMath.Anchored((DateTime)value)));
case "gt":
return query.DateRange(d => d.Field(propertyExpression).GreaterThan(DateMath.Anchored((DateTime)value)));
case "gte":
return query.DateRange(d => d.Field(propertyExpression).GreaterThanOrEquals(DateMath.Anchored((DateTime)value)));
case "startswith":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "endswith":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "contains":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "doesnotcontain":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "isempty":
throw new ArgumentException("Unable to apply less than to date and time queries");
case "isnotempty":
throw new ArgumentException("Unable to apply less than to date and time queries");
default:
throw new ArgumentOutOfRangeException();
}
}
private QueryContainer CreateStringFilter(QueryContainerDescriptor<T> query,
Expression<Func<T, object>> propertyExpression,
string filterOperator, object value)
{
switch (filterOperator)
{
case "eq":
return query.QueryString(qs => qs.Fields(fs => fs.Field(propertyExpression)).Query((string)value));
case "neq":
return query.Bool(b => b.MustNot(x => x.QueryString(qs => qs.Fields(fs => fs.Field(propertyExpression)).Query((string)value))));
case "isnull":
return query.QueryString(qs => qs.Fields(fs => fs.Field(propertyExpression)).Query(null));
case "isnotnull":
return query.Bool(b => b.MustNot(x => x.QueryString(qs => qs.Fields(fs => fs.Field(propertyExpression)).Query(null))));
case "lt":
throw new ArgumentException("Unable to apply less than to string queries");
case "lte":
throw new ArgumentException("Unable to apply less than equal to string queries");
case "gt":
throw new ArgumentException("Unable to apply greater than to string queries");
case "gte":
throw new ArgumentException("Unable to apply greater than equal to string queries");
case "startswith":
return query.Bool(b => b.Should(x => x.Term(propertyExpression, value)));
case "endswith":
return query.Bool(b => b.Should(x => x.Term(propertyExpression, value)));
case "contains":
return query.Match(qs => qs.Field(propertyExpression).Query((string)value).Operator(Operator.And));
case "doesnotcontain":
return query.Bool(b => b.MustNot(x => x.Match(qs => qs.Field(propertyExpression).Query((string)value).Operator(Operator.And))));
case "isempty":
return query.QueryString(qs => qs.Fields(fs => fs.Field(propertyExpression)).Query(""));
case "isnotempty":
return query.Bool(b => b.MustNot(x => x.QueryString(qs => qs.Fields(fs => fs.Field(propertyExpression)).Query(""))));
default:
throw new ArgumentOutOfRangeException();
}
}
private System.Linq.Expressions.Expression<Func<T, object>> MapToProperty(string fieldName)
{
switch (fieldName)
{
//return expressions from property names
default:
throw new ArgumentOutOfRangeException();
}
}
private Type MapToType(string fieldName)
{
switch (fieldName)
{
// Map field name to type
// return typeof(String);
default:
throw new ArgumentOutOfRangeException();
}
}
}

You will need to build in your own express and type mapping for properties, but otherwise this is built for Strings and DateTimes. From this base you should be able to implement different types and queries you would need for the Kendo Grid to work with ElasticSearch.

Creating an ASP.NET Core application for Raspberry Pi

As a part of the Wren Hyperion solution, an ASP.NET Core application will run on an ARM based Linux OS (we are building a POC for the Raspberry Pi and Raspian).  Here are the steps on how you can get started with ASP.NET Core on Raspian:

  • Setup a Raspberry Pi with Raspian
  • Install .NET Core
  • Create your ASP.NET Core solution
  • Publish to the Raspberry Pi
  • Install .NET Core to the Raspberry Pi
  • Run your application on the Raspberry Pi

Setup a Raspberry Pi with Raspian

Follow the guides on the Raspberry Pi website to install Raspian on your Pi.

Install .NET Core

  1. Download and Install

    To start building .NET apps you just need to download and install the .NET SDK (Software Development Kit) for Windows.

    Download .NET SDK

    A video frame showing a Windows command prompt. Select this image to start playing the video.

    Video: Creating a Simple .NET Application on Windows

  2. Get an editor

    Visual Studio is a fully-featured integrated development environment (IDE) for developing .NET apps on Windows.

    Download Visual Studio

    Select the .NET Core cross-platform development workload during installation:

    A screenshot of the Visual Studio Installer. The Workload screen is shown, and '.NET Core cross-platform development' is selected.

  3. More info

    While you wait for Visual Studio to install, you can keep learning with the .NET Quick Starts. In the first Quick Start you’ll learn about collections.

    Quick start: Collections

    Once Visual Studio is installed, come back and build your first .NET app using Visual Studio.

Create your ASP.NET Core Solution

  1. Create a new .NET Core project.

    On macOS and Linux, open a terminal window. On Windows, open a command prompt.

    dotnet new razor -o aspnetcoreapp
  2. Run the app.Use the following commands to run the app:
    cd aspnetcoreapp
    dotnet run
    
  3. Browse to http://localhost:5000
  4. Open Pages/About.cshtml and modify the page to display the message “Hello, world! The time on the server is @DateTime.Now “:
    @page
    @model AboutModel
    @{
        ViewData["Title"] = "About";
    }
    <h2>@ViewData["Title"]</h2>
    <h3>@Model.Message</h3>
    
    <p>Hello, world! The time on the server is @DateTime.Now</p>
    
  5. Browse to http://localhost:5000/About and verify the changes.

Publish to the Raspberry Pi

On macOS and Linux, open a terminal window. On Windows, open a command prompt and run:
dotnet clean .
dotnet restore .
dotnet build .
This will rebuild the solution. Once that is complete run the following command:
dotnet publish . -r linux-arm
This will generate all the files needed for running the solution on the Raspberry Pi. After this command completes generating the needed files, the files need to be deployed to the Pi. Run the following command in Powershell from the publish folder:
& pscp.exe -r .\bin\Debug\netcoreapp2.0\ubuntu.16.04-arm\publish\* ${username}@${ip}:${destination}
Where username is the username for the Pi (default “pi”), ip is the ip address of the Pi, and destination is the folder the files will be published to.

Install .NET Core to the Raspberry Pi

This section is sourced from Dave the Engineer’s post on the Microsoft blog website.

The following commands need to be run on the Raspberry Pi whilst connected over an SSH session or via a terminal in the PIXEL desktop environment.

  • Run sudo apt-get install curl libunwind8 gettext. This will use the apt-get package manager to install three prerequiste packages.
  • Run curl -sSL -o dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Runtime/release/2.0.0/dotnet-runtime-latest-linux-arm.tar.gz to download the latest .NET Core Runtime for ARM32. This is refereed to as armhf on the Daily Builds page.
  • Run sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet to create a destination folder and extract the downloaded package into it.
  • Run sudo ln -s /opt/dotnet/dotnet /usr/local/bin` to set up a symbolic link…a shortcut to you Windows folks 😉 to the dotnet executable.
  • Test the installation by typing dotnet –help.

  • Try to create a new .NET Core project by typing dotnet new console. Note this will prompt you to install the .NET Core SDK however this link won’t work for Raspian on ARM32. This is expected behaviour.

Run your application on the Raspberry Pi

Finally to run your application on the Raspberry Pi, navigate to the folder where the application was published and run the following command:

dotnet run

You can then navigate to the website hosted by the Raspberry Pi by navigating to http://{your IP address here}:5000.