Subido por Jacob Mellado Toledo

DNCMag-Issue44

Anuncio
CONTENTS
SEPTEMBER 2019
WHAT’S NEW
IN
GRPC WITH
ASP.NET CORE
3.0
.NET CORE 3.0
24
AZURE
C# AND .NET
104
Automation in
Microsoft Azure
92
Developing Mobile
applications in
.NET
06
Diving into
Azure CosmosDB
78
Function Parameters
in C#
VISUAL STUDIO
54
Streamlining your
VS Project Setup
04
2
32
07
10
13
DEVOPS
68
Devops Timeline
16
19
21
23
26
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
29
31
33
LETTER FROM
THE EDITOR
Dear Readers, The response for the 7th
Anniversary edition was FABULOUS!
With over 137K downloads, the efforts
put in by the DotNetCurry team for the
anniversary edition, was all worthwhile!
Thank you readers!
As you are aware, some updates and recent announcements were
made around C# 8, .NET Core 3 and such. This edition aims at
keeping you up-to-date with some of these changes.
We have a bouquet of exclusive articles for you covering
.NET Core 3.0, ASP.NET Core gRPC, Visual Studio Productivity,
Azure Cosmos DB, Azure Automation, Design Patterns, DevOps
and more.
I also wanted to take this opportunity to express my
gratitude to our patrons who purchased our latest C# and .NET
Book. Patrons, your continuous support and a few sponsorships
has helped us release 44 editions for free so far, and will help
us to continue our efforts in the future too! Cheers to all of you!
How was this edition?
Contributing Authors :
Damir Arh
Daniel Jimenez Garcia
Dobromir Nikolov
Gouri Sohoni
Subodh Sohoni
Tim Sommer
Yacoub Massad
Technical Reviewers :
Yacoub Massad
Tim Sommer
Subodh Sohoni
Mahesh Sabnis
Gouri Sohoni
Gerald Verslius
Dobromir Nikolov
Daniel Jimenez Garcia
Damir Arh
Next Edition : Nov 2019
Make sure to reach out to me directly with your comments
and feedback on twitter @dotnetcurry or email me at
suprotimagarwal@dotnetcurry.com.
Happy Learning!
Copyright @A2Z Knowledge Visuals Pvt.
Ltd.
Art Director : Minal Agarwal
Suprotim Agarwal
Editor in Chief
Editor In Chief :
Suprotim Agarwal (suprotimagarwal@
dotnetcurry.com)
Disclaimer :
Reproductions in whole or part prohibited except by written permission. Email requests to “suprotimagarwal@
dotnetcurry.com”. The information in this magazine has been reviewed for accuracy at the time of its publication,
however the information is distributed without any warranty expressed or implied.
Windows, Visual Studio, ASP.NET, Azure, TFS & other Microsoft products & technologies are trademarks of the Microsoft group of companies.
‘DNC Magazine’ is an independent publication and is not affiliated with, nor has it been authorized, sponsored, or otherwise approved by
Microsoft Corporation. Microsoft is a registered trademark of Microsoft corporation in the United States and/or other countries.
www.dotnetcurry.com/magazine
3
AZURE COSMOS DB
Tim Sommer
DIVING INTO
AZURE COSMOS DB
Azure Cosmos DB is Microsoft's fully managed
globally distributed, multi-model database service "for
managing data at planet-scale".
But what do those terms mean?
Does "planet-scale" mean you can scale indefinitely?
Is it really a multi-model Database?
In this tutorial, I'd like to explorer the major
components and features that define Azure Cosmos
DB, making it a truly unique database service.
6
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Purpose of this article
This article is meant for developers who want to get a grasp of the concepts, features and advantages they
get when using Azure Cosmos DB in distributed environments.
I'm not going to go touch specific detailed aspects, introductions and code samples. These can already be
found in abundance across the web, including different tutorials on DotNetCurry.
On the other hand, at times, you need extended knowledge and gain some practical experience to be
able to follow the official Azure Cosmos DB documentation. Hence, my aim is to meet the theoretical
documentation with a more practical and pragmatic approach, offering a detailed guide into the realm of
Azure Cosmos DB, but maintaining a low learning curve.
What is NoSQL?
To start off, we need a high-level overview of the purpose and features of Azure Cosmos DB. And to do that,
we need to start at the beginning.
Azure Cosmos DB is a "NoSQL" database.
But what is "NoSQL", and why and how do "NoSQL" databases differ from traditional "SQL" databases?
As defined in Wikipedia; SQL, or Structured Query Language, is a "domain specific" language. It is designed
for managing data held in relational database management systems (RDMS). As the name suggests, it is
particularly useful in handling structured data, where there are relations between different entities of that
data.
So, SQL is a language, not tied to any specific framework or database. It was however standardized (much
like ECMA Script became the standard for JavaScript) in 1987, but despite its standardization, most SQL
code is not completely portable among different database systems. In other words, there are lots of
variants, depending on the underlying RDMS.
SQL has become an industry standard. Different flavours include T-SQL (Microsoft SQL Server), PS/SQL
(Oracle) and PL/pgSQL (PostgreSQL); and the standard supports all sorts of database systems including
MySQL, MS SQL Server, MS Access, Oracle, Sybase, Informix, Postgres and many more.
Even Azure Cosmos DB supports a SQL Like syntax to query NoSQL data, but more on that later.
So that's SQL, but what do we mean when we say “NoSQL”?
Again, according to Wikipedia, a NoSQL (originally referring to “non-SQL” or “non-relational”) database provides
a mechanism for storage and retrieval of data that is modelled different than the tabular relations used in
relational databases. It is designed specifically for (but is certainly not limited to) handling Big Data.
Big Data often is defined by four V's: Volume (high count of record with quantities of data that reach almost
incomprehensible proportions), Variety (high difference between different data), Velocity (how fast data is
coming in and how fast it is processed) and Veracity (refers to the biases, noise and abnormality in data).
www.dotnetcurry.com/magazine
7
Figure 1: Big Data, Four V’s
Because of their history, strict schema rules and lack (or let's call it challenges) of scaling options;
relational databases are simply not optimized to handle such enormous amounts of data.
Relational databases were mostly designed to scale up, by increasing the CPU power and RAM of the
hosting server. But, for handling Big Data, this model just doesn’t suffice.
For one, there are limits to how much you can scale up a server. Scaling up also implies higher costs: the
bigger the host machine, the higher the end bill will be. And, after you hit the limits of up-scaling, the only
real solution (there are workarounds, but no real solutions) is to scale out, meaning the deployment of the
database on multiple servers.
Don't get me wrong, relational databases like MS SQL Server and Oracle are battle tested systems, perfectly
capable in solving a lot of problems in the current technical landscape. The rise of NoSQL databases does
not, in any way, take away their purpose.
But for Big Data, you need systems that embrace the “distributed model”, meaning scale-out should be
embraced as a core feature.
In essence, that is what NoSQL is all about - allowing you to distribute databases over different servers,
allowing them to handle gigabytes and even petabytes of data.
As they are designed especially for these kinds of cases, NoSQL databases can handle massive amounts of
data whilst ensuring and maintaining low latency and high availability. NoSQL databases are simply more
suited to handle Big Data, than any kind of RDMS.
8
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
NoSQL databases are also considered "schema free". This essentially means that you can have unlimited
different data-models (or schemas) within a database. To be clear, the data will always have a schema (or
type, or model), but because the schema can vary for each item, there is no schema management. Whilst
schema changes in relational databases can pose a challenge, in NoSQL databases, you can integrate
schema changes without downtime.
What is Azure Cosmos DB?
So, we now have fairly good idea of what defines a system as a NoSQL database. But what is Azure Cosmos
DB, and how does it differ from other frameworks and (NoSQL) databases already available?
Azure Cosmos DB, announced at the Microsoft Build 2017 conference, is an evolution of the former Azure
Document DB, which was a scalable NoSQL document database with Low Latency, and hosted on Microsoft's
Azure platform.
Azure Cosmos DB allows virtually unlimited scale and automatically manages storage with server-side
partitioning to uphold performance. As it is hosted on Azure, a Cosmos DB can be globally distributed.
Cosmos DB supports multiple data models, including JSON, BSON, Table, Graph and Columnar, exposing
them with multiple APIs - which is probably why the name "Document DB" didn't suffice any more.
Azure Cosmos DB was born!
Let's sum up the core feature of Azure Cosmos DB:
•
•
•
•
•
•
•
Turnkey global distribution.
Single-digit millisecond latency.
Elastic and unlimited scalability.
Multi-model with wire protocol compatible API endpoints for Cassandra, MongoDB, SQL, Gremlin and
Table along with built-in support for Apache Spark and Jupyter notebooks.
SLA for guarantees on 99.99% availability, performance, latency and consistency
Local emulator
Integration with Azure Functions and Azure Search.
Let's look at these different features, starting with what makes Azure Cosmos DB a truly unique database
service: multi-model with API endpoint support.
Multi-model
SQL (Core) API
Azure Cosmos DB SQL API is the primary API, which lives on from the former Document DB. Data is stored
in JSON format. The SQL API provides a formal programming model for rich queries over JSON items. The
SQL used is essentially a subset, optimized for querying JSON documents.
Azure Cosmos DB API for Mongo DB
The MongoDB API provides support for MongoDB data (BSON). This will appeal to existing MongoDB
developers, because they can enjoy all the features of Cosmos DB, without changing their code.
www.dotnetcurry.com/magazine
9
Cassandra API
The Cassandra API, using columnar as data model, requires you to define the schema of your data up front.
Data is stored physically in a column-oriented fashion, so it's still okay to have sparse columns, and it has
good support for aggregations.
Gremlin API
The Gremlin API (Graph traversal language) provides you with a "graph database", which allows you to
annotate your data with meaningful relationships. You get the graph traversal language that leverages
these annotations allowing you to efficiently query across the many relationships that exist in the database.
Table API
The Table API is an evolution of Azure Table Storage. With this API, each entity consists of a key and a value
pair. But the value itself, can again contain set of key - value pairs.
Spark
The Spark API enables real-time machine learning and AI over globally distributed data-sets by using builtin support for Apache Spark and Jupyter notebooks.
The choice is yours!
The choice of which API (and underlying data model) to use, is crucial. It defines how you will be
approaching your data and how you will query it. But, regardless of which API you choose, all the key
features of Azure Cosmos DB will be available.
Internally, Cosmos DB will always store your data as "ARS", or Atom Record Sequence. This format gets
projected to any supported data model. No matter which API you choose, your data is always stored as keyvalues; where the values can be simple values or nested key-values (think of JSON and BSON). This concept
allows developers to have all the features, regardless of the model and API they choose.
In this article, we will focus on the SQL API, which is still the primary API that lives on from the original
Document DB Service.
Azure Cosmos DB Accounts, Databases and
Containers
An Azure Cosmos database is, in essence, a unit of management for a set of schema-agnostic containers.
They are horizontally partitioned across a set of machines using "Physical partitions" hosted within one or
more Azure regions. All these databases are associated with one Azure Cosmos DB Account.
10
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Figure 2: Simplified Azure Cosmos DB top-level overview
To create an Azure Cosmos DB account, we need to input a unique name, which will represent your endpoint
by adding “.documents.azure.com”. Within this account, you can create any number of databases. An account
is associated with one API. In the future, it might be possible to freely switch between APIs within the
account. At the moment of writing, this is not yet the case. All the databases within one account will always
exploit the same API.
You can create an Azure Cosmos DB Account on the Azure portal. I'll quickly walk through the creation of an
Azure Cosmos DB account, creating a database and a couple of collections which will serve as sample data
for the rest of this article.
Figure 3: Create Azure Cosmos DB Account
www.dotnetcurry.com/magazine
11
For this article, we'll be using a collection of volcanoes to demonstrate some features. The document looks
like this:
{
}
"VolcanoName": "Acatenango",
"Country": "Guatemala",
"Region": "Guatemala",
"Location": {
"type": "Point",
"coordinates": [
-90.876,
14.501
]
},
"Elevation": 3976,
"Type": "Stratovolcano",
"Status": "Historical",
"Last Known Eruption": "Last known eruption in 1964 or later",
"id": "a6297b2d-d004-8caa-bc42-a349ff046bc4"
For this data model, we'll create a couple of collections, each with different partition keys. We’ll look into
that concept in depth later on in the article, but for now, I’ll leave you with this: A partition key is a predefined hint Azure Cosmos DB uses to store and locate your documents.
For example, we’ll define three collections with different partition keys:
•
•
•
By country (/Country)
By Type (/Type)
By Name (/Name)
You can use the portal to create the collections:
Figure 4: Creating Collections and database using Azure portal
12
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
All right! We have now set up some data containers with sample data which can be used to explain the core
concepts. Let's dive in!
Throughput, horizontal partitioning and global
distribution
Throughput
In Azure Cosmos DB, performance of a container (or a database) is configured by defining its throughput.
Throughput defines how many requests can be served within a specific period, and how many concurrent
requests can always be served within any given second. It defines how much load your database and/or
container needs to be able to handle, at any given moment in time. It is not to be mistaken with latency,
which defines how fast a response for a given request is served.
Managing throughput and ensuring predictable performance is configured by assigning Request Units (or
RUs). Azure Cosmos DB uses the assigned RUs to ensure that the requested performance can be handled,
regardless of how demanding the workload you put on the data in your database, or your collection.
Request Units are a blended measurement of computational cost for any given request. Required CPU
calculations, memory allocation, storage, I/O and internal network traffic are all translated into one unit.
Simplified, you could say that RUs are the currency used by Azure Cosmos DB.
The Request Unit concept allows you to disregard any concerns about hardware. Azure Cosmos DB, being
a database service, handles that for you. You only need to think about the amounts of RUs should be
provisioned for a container.
To be clear, a Request Unit is not the same as a Request, as all requests are not considered equal. For example,
read requests generally require less resources than write requests. Write requests are generally more
expensive, as they consume more resources. If your query takes longer to process, you'll use more resources;
resulting in a higher RU cost rate.
For any request, Azure Cosmos DB will tell you exactly how many RUs that request consumed. This allows
for predictable performance and costs, as identical requests will always consistently cost the same number of
RUs.
In simple terms: RUs allow you to manage and ensure predictable performance and costs for your database and/
or data container.
If we take the sample volcano data, you can see the same RU cost for inserting or updating each item.
Figure 5: Insert or update an item in a data container
www.dotnetcurry.com/magazine
13
On the other hand, if we read data, the RU cost will be lower. And for every identical read request, the RU
cost will always be the same.
Figure 6: Query RU cost
When you create a Data Container in Cosmos DB, you reserve the number of Request Units per second (RU/s)
that needs to be serviced for that container. This is called "provisioning throughput".
Azure Cosmos DB will guarantee that the provisioned throughput can be consumed for the container every
second. You will then be billed for the provisioned throughput monthly.
Your bill will reflect the provisions you make, so this is a very important step to take into consideration. By
provisioning throughput, you tell Azure Cosmos DB to reserve the configured RU/s, and you will be billed for
what you reserve, not for what you consume.
Should the situation arise that the provisioned RU/s are exceeded, further requests are "throttled". This
basically means that the throttled request will be refused, and the consuming application will get an error.
In the error response, Azure Cosmos DB will inform you how much time to wait before retrying.
So, you could implement a fallback mechanism fairly easily. When requests are throttled, it almost always
means that the provisioned RU/s is insufficient to serve the throughput your application requires. So, while
it is a good idea to provide a fallback "exceeding RU limit" mechanism, note that you will almost always
have to provision more throughput.
Request Units and billing
Provisioning throughput is directly related to how much you will be billed monthly. Because unique
requests always result in the same RU cost, you can predict the monthly bill in an easy and correct way.
When you create an Azure Cosmos DB container with the minimal throughput of 400 RU/s, you'll be billed
roughly 24$ per month. To get a grasp of how many RU/s you will need to reserve, you can use the Request
Unit Calculator. This tool allows you to calculate the required RU/s after setting parameters as item size,
reads per second, writes per second, etc. If you don't know this information when you create your containers,
don't worry, throughput can be provisioned on the fly, without any downtime.
14
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
The calculator can be accessed here: https://cosmos.azure.com/capacitycalculator/
Figure 7: Azure Cosmos DB Request Unit Calculator
Apart from the billing aspect, you can always use the Azure Portal to view detailed charts and reports on
every metric related to your container usage.
Partitioning
Azure Cosmos DB allows you to massively scale out containers, not only in terms of storage, but also in
terms of throughput. When you provision throughput, data is automatically partitioned to be able to handle
the provisioned RU/s you reserved.
Data is stored in physical partitions, which consist of a set of replicas (or clones), also referred to as replica
sets. Each replica set hosts an instance of the Azure Cosmos database engine, making data stored within
the partition durable, highly available, and consistent. Simplified, a physical partition can be thought of as a
fixed-capacity data bucket. Meaning that you can't control the size, placement, or number of partitions.
To ensure that Azure Cosmos DB does not need to investigate all partitions when you request data, it
requires you to select a partition key.
For each container, you'll need to configure a partition key. Azure Cosmos DB will use that key to group
items together. For example, in a container where all items contain a City property, you can use City as the
partition key for that container.
www.dotnetcurry.com/magazine
15
Figure 8: Image logical partition within physical partition
Groups of items that have specific values for City, such as London, Brussels and Washington will form distinct
logical partitions.
Partition key values are hashed, and Cosmos DB uses that hash to identify the logical partition the data
should be stored in. Items with the same partition key value will never be spread across different logical
partitions.
Internally, one or more logical partitions are mapped to a physical partition. This is because, while
containers and logical partitions can scale dynamically; physical partitions cannot i.e. you would wind up
with way more physical partitions than you would actually need.
If a physical partition gets near its limits, Cosmos DB automatically spins up a new partition, splitting and
moving data across the old and new one. This allows Cosmos DB to handle virtually unlimited storage for
your containers.
Any property in your data model can be chosen as the partition key. But only the correct property will result
in optimal scaling of your data. The right choice will produce massive scaling, while choosing poorly will
impede Azure Cosmos DB's ability to scale your data (resulting in so called hot partitions).
So even though you don't really need to know how your data eventually gets partitioned, understanding it
in combination with a clear view of how your data will be accessed, is absolutely vital.
16
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Choosing your partition key
Firstly, and most importantly, queries will be most efficient if they can be scoped to one logical partition.
Next to that, transactions and stored procedures are always bound to the scope of a single partition. So,
these make the first consideration on deciding a partition key.
Secondly, you want to choose a partition key that will not cause throughput or storage bottlenecks.
Generally, you’ll want to choose a key that spreads the workload evenly across all partitions and evenly over
time.
Remember, when data is added to a container, it will be stored into a logical partition. Once the physical
partition hosting the logical grows out of bounds, the logical partition is automatically moved to a new
physical partition.
You want a partition key that yields the most distinct values as possible. Ideally you want distinct values in
the hundreds or thousands. This allows Azure Cosmos DB to logically store multiple partition keys in one
physical partition, without having to move partitions behind the scenes.
Take the following rules into consideration upon deciding partition keys:
•
A single logical partition has an upper limit of 10 GB of storage. If you max out the limit of this
partition, you'll need to manually reconfigure and migrate data to another container. This is a disaster
situation you don’t want!
•
To prevent the issue raised above, choose a partition key that has a wide range of values and access
patterns, that are evenly spread across logical partitions.
•
Choose a partition key that spreads the workload evenly across all partitions and evenly over time.
•
The partition key value of a document cannot change. If you need to change the partition key, you'll
need to delete the document and insert a new one.
•
The partition key set in a collection can never change and has become mandatory.
•
Each collection can have only one partition key.
Let's try a couple of examples to make sense of how we would choose partition keys, how hot partitions
work and how granularity in partition keys can have positive or negative effects in the partitioning of your
Azure Cosmos DB. Let’s take the sample volcano data again to visualize the possible scenarios.
Partition key “Type”
When I first looked through the volcano data, “type” immediately stood out as a possible partition key. Our
partitioned data looks something like this:
www.dotnetcurry.com/magazine
17
Figure 11: Volcanoes by type
To be clear, I’m in no means a volcano expert, the data repo is for sample purposes only. I have no idea if
the situation I invented is realistic - it is only for demo purpose.
Okay, now let’s say we are not only collecting meta-data for each volcano, but also collecting data for
each eruption. Which means we look at the collection from a Big Data perspective. Let’s say that “Lava
Cone” volcanoes erupted three times as much this year, than previous years. But as displayed in the image,
the “Lava Cone” logical partition is already big. This will result in Cosmos DB moving into a new Physical
Partition, or eventually, the logical partition will max out.
So, type, while seemingly a good partition key, it is not such a good choice if you want to collect hour to
hour data of erupting events. For the meta data itself, it should do just fine.
The same goes for choosing “Country” as a partition key. If for one reason or another, Russia gets more
eruptions than other countries, its size will grow. If the eruption data gets fed into the system in real-time,
you can also get a so-called hot partition, with Russia taking up all the throughput in the container - leaving
none left for the other countries.
If we were collecting data in real-time on a massive scale, the only good partition key in this case is by
“Name”. This will render the most distinct values, and should distribute the data evenly amongst the
partitions.
Microsoft created a very interesting and comprehensive case study on how to model and partition data on
Azure Cosmos DB using a real-world example. You can find the documentation here: https://docs.microsoft.
com/en-us/azure/cosmos-db/partition-data.
Cross partition Queries
You will come across situations where querying by partition key will not suffice. Azure Cosmos DB can span
queries across multiple partitions, which basically means that it will need to look at all partitions to satisfy
18
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
the query requirements. I hope it’s clear at this point that you don't want this to happen too frequently, as it
will result in high RU costs.
When cross partition queries are needed, Cosmos DB will automatically “fan out the query”, i.e. it will visit
all the partition in parallel, gather the results, and then return all results in a single response.
This is more costly than running queries against single partitions, which is why these kinds of queries
should be limited and avoided as much as possible. Azure Cosmos DB enforces you to explicitly enable
this behaviour by setting it in code or via the portal. Writing a query without the partition key in the where
clause, and without explicitly setting the "EnableCrossPartitionQuery" property, will fail.
Back to the sample volcano data, we see the RU cost rise when we trigger a cross partition query. As we
saw earlier, the RU cost for querying a collection with partition key “Country” was 7.240 RUs. If we Query
that collection by type, the RU cost is 10.58. Now, this might not be a significant rise, but remember, the
sample data is small, and the query is executed only once. In a distributed application model, this could be
quite catastrophic!
Figure 12: Cross partition query RU cost.
Indexing
Azure Cosmos DB allows you to store data without having to worry about schema or index management. By
default, Azure Cosmos DB will automatically index every property for all items in your container.
This means that you don’t really need to worry about indexes, but let’s take a quick look at the indexing
modes Azure Cosmos DB provides:
•
Consistent: This is the default setting. The indexes are updated synchronously as you create, update or
delete items.
•
Lazy: Updates to the index are done at a much lower priority level, when the engine is not doing any
other work. This can result in inconsistent or incomplete query results, so using this indexing mode is
recommended against.
•
None: Indexing is disabled for the container. This is especially useful if you need to big bulk
www.dotnetcurry.com/magazine
19
operations, as removing the indexing policy will improve performance drastically. After the bulk
operations are complete, the index mode can be set back to consistent, and you can check the
IndexTransformationProgress property on the container (SDK) to the progress.
You can include and exclude property paths, and you can configure three kinds of indexes for data.
Depending on how you are going to query your data, you might want to change the index on your colums:
•
Hash index: Used for equality indexes.
•
Range index: This index is useful if you have queries where you use range operations (< > !=), ORDER BY
and JOIN.
•
Spatial Index: This index is useful if you have GeoJSON data in your documents. It supports Points,
LineStrings, Polygons, and MultiPolygons.
•
Composite Index: This index is used for filtering on multiple properties.
As indexing in Azure Cosmos DB is performed automatically by default, I won’t include more information
here. Knowing how to leverage these different kind of indexing (and thus deviating from the default) in
different scenarios can result in lower RU cost and better performance. You can find more info in the official
Microsoft documentation, specific to a range of Use Cases.
Global distribution
As said in the intro, Azure Cosmos DB can be distributed globally and can manage data on a planet scale.
There are a couple of reasons why you would use Geo-replication for your Cosmos databases:
Latency and performance: If you stay within the same Azure region, the RU/s reserved on a container are
guaranteed by the replicas within that region. The more replicas you have, the more available your data will
become. But Azure Cosmos DB is built for global apps, allowing your replicas to be hosted across regions.
This means the data can be allocated closer to consuming applications, resulting in lower latency and high
availability.
Azure Cosmos DB has a multi-master replication protocol, meaning that every region supports both writes
and reads. The consuming application is aware of the nearest region and can send requests to that region.
This region is identified without any configuration changes. As you add more regions, your application does
not need to be paused or stopped. The traffic managers and load balancers built into the replica set, take
care of it for you!
Disaster recovery: By Geo-replicating your data, you ensure the availability of your data, even in the events
of major failure or natural disasters. Should one region become unavailable, other regions automatically
take over to handle requests, for both write and read operations.
Turnkey Global Distribution
In your Azure Cosmos DB Account, you can add or remove regions with the click of a mouse! After you select
multiple regions, you can choose to do a manual fail over, or select automatic fail over priorities in case
of unavailability. So, if a region should become unavailable for whatever reason, you control which region
behaves as a primary replacement for both reads and writes.
20
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
You can Geo-replicate your data in every Azure Region that is currently available:
Figure 13: Azure regions currently available
https://azure.microsoft.com/en-us/global-infrastructure/regions/
In theory, global distribution will not affect the throughput of your application. The same RUs will be used
for request between regions i.e. a query to one region will have the same RU cost for the same query in
another region. Performance is calculated in terms of latency, so actual data over the wire will be affected
if the consuming application is not hosted in the same Azure Region. So even though the costs will be the
same, latency is a definite factor to take into consideration when Geo-replicating your data.
While there are no additional costs for global distribution, the standard Cosmos DB Pricing will get
replicated. This means that your provisioned RU/s for one container will also be provisioned for its Georeplicated instances. So, when you provisioned 400 RU/s for one container in one region; you will reserve
1200 RU/s if you replicate that container to two other regions.
Multiple Region Write accounts will also require extra RUs. These are used for operations such as managing
write conflicts. These extra RUs are billed at the average cost of the provisioned RUs across your account's
regions. Meaning if you have three regions configured at 400 RU/s (billed around 24$ per region = 70 $),
you'll be billed an additional 24$ for the extra write operations.
This can all be simulated fairly simply by consulting the Cosmos DB Pricing Calculator: https://azure.
microsoft.com/en-us/pricing/calculator/?service=cosmos-db.
www.dotnetcurry.com/magazine
21
Replication and Consistency
As Azure Cosmos DB replicates your data to different instances across multiple Azure regions, you need
ways to get consistent reads across all the replicas. By applying consistency levels, you get control over
possible read version conflicts scattered across the different regions.
There are five consistency levels that you can configure for your Azure Cosmos DB Account:
Figure 14: Consistency levels
These levels, ranging from strong to eventual, allow you to take complete control of the consistency
tradeoffs for your data containers. Generally, the stronger your consistency, the lower your performance in
terms of latency and availability. Weaker consistency results in higher performance but has possible dirty
reads as a tradeoff.
Strong consistency ensures that you never have a dirty read, but this comes at a huge cost in latency. In
strong consistency, Azure Cosmos DB will enforce all reads to be blocked until all the replicas are updated.
Eventual consistency, on the other side of the spectrum, will offer no guarantee whatsoever that your data
is consistent with the other replicas. So you can never know whether the data you are reading is the latest
version or not. But this allows Cosmos DB to handle all reads without waiting for the latest writes, so
queries can be served much faster.
In practice, you only must take consistency into consideration when you start Geo-replicating your Azure
Cosmos databases. It's almost impossible to experience a dirty read if your replicas are hosted within
one Azure Region. Those replicas are physically located closely to each other, resulting in data transfer
almost always within 1ms. So even with the strong consistency level configured, the delay should never be
problematic for the consuming application.
The problem arises when replicas are distributed globally. It can take hundreds of milliseconds to
move your data across continents. It is here that chances of dirty reads become exponential, and where
consistency levels come into play.
Let's look at the different levels of consistency:
•
Strong: No dirty reads, Cosmos DB waits until all replicas are updated before responding to read
requests.
•
Bounded Staleness: Dirty reads are possibly but are bounded by time and updates. This means that you
can configure the threshold for dirty reads, only allowing them if the data isn't out of date. The reads
might be behind writes by at most “X” versions (i.e., “updates”) of an item or by “Y” time interval. If the
22
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
data is too stale, Cosmos DB will fall back to strong consistency.
•
Session Consistency (default): No dirty reads for writers, but possible for other consuming applications.
• All writers include a unique session key in their requests. Cosmos DB uses this session key to ensure
strong consistency for the writer, meaning a writer will always read the same the data as they wrote.
Other readers may still experience dirty reads.
•
When you use the .NET SDK, it will automatically include a session key, making it by default strongly
consistent within the session that you use in your code. So, within the lifetime of a document client,
you're guaranteed strong consistency.
•
Consistent prefix: Dirty reads are possible but can never be out-of-order across your replicas. So if the
same data gets updated six times, you might get version 4 of the data, but only if that version of the
data has been replicated to all your replicas. Once version 4 has been replicated, you'll never get a
version before that (1-3), as this level ensures that you get updates in order.
•
Eventual consistency: Dirty reads possible, no guaranteed order. You can get inconsistent results in
a random fashion, until eventually all the replicas get updated. You simply get data from a replica,
without any regards of other versions in other replicas.
When Bounded staleness and Session don't apply strong consistency, they fallback to Consistent prefix, not
eventual consistency. So even in those cases, reads can never be out-of-order.
You can set the preferred consistency level for the entire Cosmos DB Account i.e. every operation for all
databases will use that default consistency strategy. This default can be changed at any time.
It is however possible to override the default consistency at request level, but it is only possible to weaken
the default. So, if the default is Session, you can only choose between Consistent or Eventual consistency
levels. The level can be selected when you create the connection in the .NET SDK.
Conclusion
Azure Cosmos DB is a rich, powerful and an amazing database service, that can be used in a wide variety of
situations and use cases. With this article, I hope I was able to give you a simplified and easy to grasp idea
of its features. Especially in distributed applications and in handling Big Data, Azure Cosmos DB stands out
of the opposition. I hope you enjoyed the read!
Tim Sommer
Author
Tim Sommer lives in the beautiful city of Antwerp, Belgium. He is passionate about computers and
programming for as long as he can remember. He’s a speaker, teacher and entrepreneur. He is also
a former Windows Insider MVP. But most of all, he’s a developer, an architect, a technical specialist and a
coach; with 8+ years of professional experience in the .NET framework.
Thanks to Mahesh Sabnis for reviewing this article.
www.dotnetcurry.com/magazine
23
AZURE DEVOPS
Damir Arh
WHAT’S NEW IN
.NET CORE 3.0?
This article is an overview of .NET Core 3.0 covering all
the new features and improvements it’s bringing to the
.NET ecosystem.
A
fter more than two
long-term support (LTS)
As a companion to .NET Core
years since the
version will be .NET Core 3.1 in
3.0, .NET Standard 2.1 is also
November 2019.
being released. In comparison
release of .NET Core 2.0 in
August 2017, .NET Core 3.0
to .NET Standard 2.0, the latest
was released as the next
To develop for .NET Core 3.0,
2.1 includes many new classes
major version of .NET Core.
you need an up-to-date version
and methods which were
It’s been supported for use in
of Visual Studio 2019. There’s
added to .NET Core 3.0 making
production since July 2019
no support for it in Visual
it possible for cross-platform
when Preview 7 of .NET Core
Studio 2017.
class libraries to use them as
3.0 was released. The next
24
well.
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
.NET Core. You can read more about .NET Standard in my previous
article – .NET Standard 2.0 and XAML Standard.
.NET Core 3.0 is also the first .NET runtime to fully support C#
8.0. You can read more about the new language features that it
introduces in my previous article – New C# 8 Features in Visual
Studio 2019.
The so-called .NET Core Global Tools can also be installed as local
tools in .NET Core 3.0. Global tools are command line utilities which
can be easily installed and run using the dotnet command. They
were originally introduced with .NET Core 2.1, but they could only be
installed globally (as their name implies) which made them always
available from the command line. In .NET Core 3.0, they can also be
installed locally as a part of a .NET Core project or solution and as
such automatically available to all developers working on the same
code repository. You can read more about .NET Core Global Tools in
my previous article – .NET Core Global Tools - (What are Global Tools,
How to Create and Use them).
Despite that, it’s still a good
idea to write your libraries for
.NET Standard 2.0 whenever
possible because this will
make them available to
different .NET runtimes, e.g.
.NET framework which isn’t
going to support .NET Standard
2.1 and previous versions of
3.0
www.dotnetcurry.com/magazine
25
Table 1: .NET Core application models and libraries
Windows-Specific Features
Since further development of .NET framework stopped with version 4.8, .NET Core 3.0 has a big focus on
Windows-specific features which were previously not supported. The goal is to make .NET Core a way
forward for Windows developers who currently use .NET framework. Unlike almost all the other parts of
.NET Core so far, these new features will not be cross-platform and will only work on Windows because they
heavily rely on services provided by the operating system.
Windows Desktop
The most prominent new feature in .NET Core 3.0 is support for development of Windows desktop
applications using Windows Forms or WPF (Windows Presentation Foundation). Both application models
are fully compatible with their .NET framework counterparts. Therefore, any existing .NET framework
applications using them can be ported to .NET Core unless they depend on some other feature that is not
supported in .NET Core, such as .NET Remoting or advanced WCF features for example.
Porting existing .NET framework applications to .NET Core is not trivial and requires some changes to the
source code as well as some testing afterwards. Because of this, it only really makes sense for applications
which are still being actively developed. Also, the designers in Visual Studio 2019 don’t work yet on .NET
Core projects. To use them, a companion .NET framework project is required which shares the files with the
.NET Core project. This workaround is required only temporarily, as support for designers will be added to
future versions of Visual Studio 2019.
Although Windows Forms and WPF in .NET Core 3.0 are mostly just ports from .NET framework, there are
two new features worth mentioning:
•
26
Windows Forms has enhanced support for high DPI screens. Since it is not fully compatible with high
DPI behavior in .NET framework, it requires additional testing and potentially changes to source code.
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
•
Both Windows Forms and WPF support XAML islands, i.e. the ability to host UWP (Universal Windows
Platform) controls inside a Windows Forms or WPF application). This is particularly useful for (although
not limited to) hosting first-party UWP controls such as WebView, Map Control, MediaPlayerElement and
InkCanvas).
To simplify deployment of Windows desktop applications developed in .NET Core 3.0, the new application
package format MSIX can be used. It’s a successor to previous Windows deployment technologies, such as
MSI, APPX and ClickOnce which can be used to publish the applications to Microsoft Store or to distribute
them separately. A dedicated Windows Application Packaging Project template in Visual Studio is available
to generate the distribution package for your application.
Support for COM Components
The significance of COM (Component Object Model) components has declined with an increased adoption
of .NET framework. However, there are still applications which rely on it, e.g. automation in several
Microsoft Office products is based on it.
.NET Core 3.0 applications on Windows can act in both COM roles:
•
As a COM client, activating existing COM components, for example to automate a Microsoft Office
application.
•
As a COM server, implementing a COM component which can be used by other COM clients.
Entity Framework
Entity Framework is Microsoft’s data access library for .NET. As part of .NET Core, a completely new version
of Entity Framework was developed from scratch – Entity Framework Core. Although its API feels familiar
to anyone who has used the original Entity Framework, it is neither source code compatible nor feature
equivalent to it. Any existing code using Entity Framework therefore needs to be rewritten in order to use
Entity Framework Core instead.
To make porting of existing Windows applications using Entity Framework to .NET Core easier, .NET Core 3.0
includes a port of the original Entity Framework in addition to a new version of Entity Framework Core.
Entity Framework 6.3 for .NET Core
Entity Framework 6.3 version in .NET Core is primarily meant for porting existing applications. New
applications should use Entity Framework Core instead which is still in active development.
The tooling for Entity Framework is currently only supported in Visual Studio 2019 on Windows. The
designer support will be added in a later Visual Studio 2019 update. Until then, the models can only be
edited from a .NET framework project similar to the approach required for the Windows Forms and WPF
designers.
However, the applications developed with Entity Framework 6.3 for .NET Core can run on any platform
which makes it suitable for porting not only Windows desktop applications but also web applications,
making them cross-platform in the process. Unfortunately, new providers are required for .NET Core.
Currently only the SQL Server provider is available. No support for SQL Server spatial types and SQL Server
www.dotnetcurry.com/magazine
27
Compact is available or planned.
Entity Framework Core 3.0
A new version of Entity Framework Core is also included with .NET Core 3.0. Among its new features, the
most important are the improvements to LINQ which make it more robust and more efficient because
larger parts of the queries are now executed on the database server instead of in the client application. As
part of this change, client-side evaluation of queries as fallback when server-side query generation fails,
was removed. Only the projection from the final Select part of the query might still be executed on the
client. This can break existing applications. Therefore, full testing of existing applications is required when
upgrading to Entity Framework Core 3.0.
Additionally, Entity Framework Core 3.0 now includes support for Cosmos DB (implemented against its SQL
API) and takes advantage of new language features in C# 8.0 (asynchronous streams). As a result, it now
targets .NET Standard 2.1 instead of .NET Standard 2.0 and therefore can’t be used with .NET framework or
older versions of .NET Core anymore.
ASP.NET Core
ASP.NET Core is probably the most widely used application model in .NET Core as all types of web
applications and services are based on it. Like Entity Framework Core, its latest version works only in .NET
Core 3.0 and isn’t supported in .NET framework.
To make applications smaller by default and reduce the number of dependencies, several libraries were
removed from the basic SDK and must now be added manually when needed:
•
Entity Framework Core must now be added to the project as a standalone NuGet package. A different
data access library can be used instead.
•
Roslyn was used for runtime compilation of views. This is now an optional feature which can be added
to a project using the Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation NuGet package.
•
JSON.NET as the library for serializing and deserializing JSON has been replaced with a built-in JSON
library with focus on high performance and minimal memory allocation. JSON.NET can still be installed
into the project and used instead.
Endpoint routing which was first introduced in .NET Core 2.2 has been further improved. Its main advantage
is better interaction between routing and the middleware. Now, the effective route is determined before the
middleware is run. This allows the middleware to inspect the route during its processing. This is especially
useful for implementing authorization, CORS configuration and similar cross-cutting functionalities as
middleware.
Server-Side Blazor
Probably the most eagerly awaited new feature in ASP.NET Core 3.0 is Blazor. In .NET Core 3.0, only serverside Blazor is production ready (this feature was named Razor Components for a while in early previews of
.NET Core).
Blazor makes client-side interactivity in a browser possible without any JavaScript code. If necessary,
28
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
JavaScript can still be invoked (e.g. to use browser APIs, such as geolocation and notifications). All the other
code is written in .NET instead. In server-side Blazor, this code runs on the server and manipulates the
markup in the browser using SignalR. For this to work, a constant connection between the browser and the
server is required. Offline scenario is not supported.
Figure 1: Blazor server-side execution model
Client-side Blazor is still in preview and will ship at an unannounced time in the future. As the name
implies, all the code runs on the client in the browser like in JavaScript-based SPAs (single page
applications). The .NET code is compiled to web assembly so that browsers can execute it.
Figure 2: Blazor client-side execution model
You can read more about Blazor in an (old albeit relevant) article by Daniel Jimenez Garcia – Blazor - .NET in
the browser.
Worker Service Template
Worker Service is a new project template for an ASP.NET Core application hosting long-running background
processes instead of responding to client requests. There are support classes available to integrate such
applications better with the hosting operating system:
•
•
On Windows, the application can act as a Windows Service.
On Linux, it can run as a systemd service.
Support for gRPC
gRPC is a modern high-performance contract-first RPC (remote procedure call) protocol developed by
Google and supported in many languages and on many platforms. It uses HTTP/2 for transfer and Protocol
Buffers (also known as Protobuf) for strongly-typed binary serialization. This makes it a great alternative to
WCF (Windows Communication Foundation) which has only limited support in .NET Core:
•
•
The client libraries only support a subset of WCF bindings.
There’s only an early open-source .NET Core port of the server libraries available.
www.dotnetcurry.com/magazine
29
While Web API can be used to implement a REST service, gRPC is better suited to remote procedure call
scenarios which were the most common use case for WCF services. Its performance benefits are most
obvious when many RPC calls and large payloads are required.
Although a gRPC library for C# has been available for a while, .NET Core 3.0 and Visual Studio 2019 now
include first-class support with project templates and tooling to make development easier.
Editorial Note: This magazine edition contains a dedicated article on gRPC with ASP.NET Core 3.0 on Page 32.
Make sure to check it out.
Changes in Deployment Model
.NET Core supports two deployment models:
•
Framework-dependent deployments require that a compatible version of the .NET Core runtime is
installed on the target computer. This allows the deployment package to be smaller because it only
needs to contain compiled application code and third-party dependencies. These are all platform
independent, therefore a single deployment package can be created for all platforms.
•
Self-contained-deployments additionally include the complete .NET Core runtime. This way the target
computer doesn’t need to have the .NET Core runtime preinstalled. As a result, the deployment package
is much larger and specific to a platform.
Before .NET Core 3.0, only self-contained deployments included an executable. Framework-dependent
deployments had to be run with dotnet command line tool. In .NET Core 3.0, framework-dependent
deployments can also include an executable for a specific target platform.
Additionally, in .NET Core 3.0, self-contained deployments support assembly trimming. This can be used
to make the deployment package smaller by only including the assemblies from the .NET Core runtime
which are used by the application. However, dynamically accessed assemblies (through Reflection) can’t
be automatically detected which can cause the application to break because of a missing assembly. The
project can be manually configured to include such assemblies, but this approach requires the deployment
package to be thoroughly tested to make sure that no required assembly is missing.
Both deployment models now also support the creation of single-file executables which include all their
dependencies (only third-party dependencies in framework-dependent deployments, also the .NET Core
runtime in self-contained deployments). Of course, these executables are always platform specific.
Startup-Time Improvements
In the field of performance, the following features focus on reducing the application startup-time:
•
Two-tier JIT (just-in-time) compiler has been available for a while, but with .NET Core 3.0, it is enabled
by default although it can still be disabled. To improve startup-time, two-tier JIT performs a faster lower
quality compilation first and makes a slower second pass at full quality later when the application is
already running.
•
Ready-to-run images introduce AOT (ahead-of-time) compilation to .NET Core. Such deployment
30
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
packages are larger because they include a native binary alongside the regular IL (intermediate
language) assemblies which are still needed. Because there’s no need for JIT compilation before startup,
the application can start even faster. For now, there’s no cross-targeting support for creating ready-torun-images, therefore they must be built on their target platform.
Conclusion
A closer look at .NET Core 3.0 makes it clear that .NET Core is maturing.
Since there’s no further development planned for .NET framework, .NET Core is now the most advanced
.NET implementation. It’s cross-platform and more performant than .NET framework.
Most of the features from .NET framework are now also included in .NET Core.
For those features which aren’t planned to be supported in .NET Core, there are alternatives available: for
Web Forms, there’s Blazor; for WCF and .NET Remoting, there’re Web API and gRPC; for Workflow Foundation
there’s Azure Logic Apps.
Now is the time to start porting the .NET framework projects you’re still actively developing, if you haven’t
done so already. This way, you’ll be ready when .NET 5 arrives, as the unified .NET runtime, by the end of
2020.
Damir Arh
Author
Damir Arh has many years of experience with Microsoft development tools; both in
complex enterprise software projects and modern cross-platform mobile applications.
In his drive towards better development processes, he is a proponent of test driven
development, continuous integration and continuous deployment. He shares his
knowledge by speaking at local user groups and conferences, blogging, and answering
questions on Stack Overflow. He is an awarded Microsoft MVP for .NET since 2012.
Thanks to Daniel Jimenez Garcia for reviewing this article.
www.dotnetcurry.com/magazine
31
ASP.NET CORE
Daniel Jimenez Garcia
GRPC WITH
ASP.NET
CORE 3.0
With the release of ASP.NET Core 3.0, amongst many other features, gRPC
will become a first-class citizen of the ecosystem with Microsoft
officially supporting and embracing it.
What this means for developers is that ASP.NET Core 3.0 now
ships with templates for building gRPC services, tooling for
defining service contracts using protocol buffers, tooling to
generate client/server stubs from said proto files and integration with
Kestrel/HttpClient.
After a brief introduction to gRPC, this article provides an overview on how
gRPC services can be created with ASP.NET Core and how to invoke these
services from .NET Core. Next, it will take a look at the cross-language
nature of gRPC by integrating with a Node.js service. We will finish by
exploring gRPC built-in security features based on TLS/SSL.
32
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Companion code of this tutorial can be found on GitHub.
Editorial Note: This article was originally written using Preview 7 of ASP.NET Core 3.0.
While Daniel has updated this article to reflect the final ASP.NET Core 3.0 release, some
screenshots may still show Preview 7.
1. THE 5 MINUTES INTRODUCTION TO GRPC
GRPC is a framework designed by Google and open sourced in 2015, which enables efficient Remote
Procedure Calls (RPC) between services. It uses the HTTP/2 protocol to exchange binary messages, which are
serialized/deserialized using Protocol Buffers.
Protocol buffers is a binary serialization protocol also designed by Google. It is highly performant by
providing a compact binary format that requires low CPU usage.
Developers use proto files to define service and message contracts which are then used to generate the
necessary code to establish the connection and exchange the binary serialized messages. When using C#,
tooling will generate strongly typed message POCO classes, client stubs and service base classes. Being a
cross-language framework, its tooling lets you combine clients and services written in many languages like
C#, Java, Go, Node.js or C++.
Protocol buffers also allow services to evolve while remaining backwards compatible. Because each field in
a message is tagged with a number and a type, a recipient can extract the fields they know, while ignoring
the rest.
Figure 1, gRPC basics
The combination of the HTTP/2 protocol and binary messages encoded with Protocol Buffers lets gRPC
achieve much smaller payloads and higher performance, than traditional solutions like HTTP REST services.
But traditional RPC calls are not the only scenario enabled by gRPC!
www.dotnetcurry.com/magazine
33
The framework takes advantage of HTTP/2 persistent connections by allowing both server and client
streaming, resulting in four different styles of service methods:
•
Unary RPC, where client performs a traditional RPC call, sending a request message and receiving a
response.
•
Server streaming RPC, where clients send an initial request but get a sequence of messages back.
•
Client streaming RPC, where clients send a sequence of messages, wait for the server to process them
and receive a single response back.
•
Bidirectional streaming RPC, where both client and server send a sequence of messages. The streams
are independent, so client and server can decide in which order to read/write the messages
So far, it’s all great news. So now you might then be wondering why isn’t gRPC everywhere, replacing
traditional HTTP REST services?
It is due to a major drawback, its browser support!
The HTTP/2 support provided by browsers is insufficient to implement gRPC, requiring solutions like gRPCWeb based on an intermediate proxy that translates standard HTTP requests into gRPC requests. For more
information, see this Microsoft comparison between HTTP REST and gRPC services.
Narrowing our focus back to .NET, there has been a port of gRPC for several years that lets you work with
gRPC services based on proto files.
So, what exactly is being changed for ASP.NET Core 3.0?
The existing Grpc.Core package is built on top of unmanaged code (the chttp2 library), and does not play
nicely with managed libraries like HttpClient and Kestrel that are widely used in .NET Core. Microsoft
is building new Grpc.Net.Client and Grpc.AspNetCore.Server packages that will avoid unmanaged code,
integrating instead with HttpClient and Kestrel. Tooling is also been improved to support a code-first
approach and both proto2 and proto3 syntax. Checkout this excellent talk from Marc Gravell for more
information.
Let’s stop our overview of gRPC here. If you were completely new to gRPC, hopefully there was enough
to pique your interest. Those of you who have been around for a while might have recognized some WCF
ideas! Let’s then start writing some code so we can see these concepts in action.
2. CREATING A GRPC SERVICE
As mentioned in the introduction, ASP.NET Core 3.0 ships with a gRPC service template. This makes creating
a new service a very straightforward task.
If you are still following using a preview release of ASP.NET Core, remember to enable .NET Core SDK preview
features in Visual Studio options:
34
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Figure 2, enabling .NET Core SDK preview features
Start by creating a new ASP.NET Core project in Visual Studio 2019. Use Orders as the name of the project,
and a different name for the solution since we will be adding more projects later. Then select the gRPC
template from the ASP.NET Core 3.0 framework:
Figure 3, using the gRPC Service template with a new ASP.NET Core application
Congratulations you have created your first fully working gRPC service! Let’s take a moment to take
a look and understand the generated code. Apart from the traditional Program and Startup classes,
you should see a Protos folder containing a file named greet.proto and a Services folder containing a
GreeterService class.
www.dotnetcurry.com/magazine
35
Figure 4, gRPC service generated by the template
These two files define and implement a sample Greeter service, the equivalent of a “Hello World” program
for a gRPC service. The greet.proto file defines the contract: i.e. which methods does the service provide, and
what are the request/response message exchanged by client and server:
syntax = "proto3";
option csharp_namespace = "Orders";
package Greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
The generated GreeterService class contains the implementation of the service:
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext
36
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
}
context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
As you can see, it inherits from a suspicious looking class named Greeter.GreeterBase, while
HelloRequest and HelloReply classes are used as request and response respectively. These classes are
generated by the gRPC tooling, based on the greet.proto file, providing all the functionality needed to send/
receive messages so you just need to worry about the method implementation!
You can use F12 to inspect the class and you will see the generated code, which will be located at obj/
Debug/netcoreapp3.0/GreetGrpc.cs (service) and obj/Debug/netcoreapp3.0/Greet.cs (messages).
It is automatically generated at build time, based on your project settings. If you inspect the project file, you
will see a reference to the Grpc.AspNetCore package and a Protobuf item:
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.23.1" />
</ItemGroup>
Grpc.AspNetCore is a meta-package including the tooling needed at design time and the libraries
needed at runtime. The Protobuf item points the tooling to the proto files it needs to generate the service
and message classes. The GrpcServices="Server" attribute indicates that service code should be
generated, rather than client code.
Let’s finish our inspection of the generated code by taking a quick look at the Startup class. You will see
how the necessary ASP.NET Core services to host a gRPC service are added with services.AddGrpc(),
while the GreeterService is mapped to a specific endpoint of the ASP.NET Core service by using
endpoints.MapGrpcService<GreeterService>(); .
2.1 Defining a contract using proto files
Let’s now replace the sample service contract with our own contract for an imaginary Orders service. This
will be a simple service that lets users create orders and later ask for the status:
syntax = "proto3";
option csharp_namespace = "Orders";
package Orders;
service OrderPlacement {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderReply) {}
rpc GetOrderStatus (GetOrderStatusRequest) returns (stream GetOrderStatusResponse) {}
}
www.dotnetcurry.com/magazine
37
message CreateOrderRequest {
string productId = 1;
int32 quantity = 2;
string address = 3;
}
message CreateOrderReply {
string orderId = 1;
}
message GetOrderStatusRequest {
string orderId = 1;
}
message GetOrderStatusResponse {
string status = 1;
}
As you can see, CreateOrder is a standard Unary RPC method while GetOrderStatus is a Server
streaming RPC method. The request/response messages are fairly self-descriptive but notice how each field
has a type and an order, allowing backwards compatibility when creating new service versions.
Let’s now rename the file as orders.proto and let’s move it to a new Protos folder located at the solution
root, since we will share this file with a client project we will create later. Once renamed and moved to its
new folder, you will need to edit the Orders.csproj project file and update the Protobuf item to include the
path to the updated file:
<Protobuf Include="..\Protos\orders.proto" GrpcServices="Server" />
Let’s now implement the OrderPlacement service we just defined.
2.2 Implementing the service
If you build the project now, you should receive a fair number of errors since the tooling will no
longer generate classes for the previous Greeter service and instead will generate them for the new
OrderPlacement service.
Rename the GreeterService.cs file as OrdersService.cs and replace its contents with the following:
public class OrderPlacementService: OrderPlacement.OrderPlacementBase
{
private readonly ILogger<OrderPlacementService> _logger;
public OrderPlacementService(ILogger<OrderPlacementService> logger)
{
_logger = logger;
}
public override Task<CreateOrderReply> CreateOrder(CreateOrderRequest request,
ServerCallContext context)
{
var orderId = Guid.NewGuid().ToString();
this._logger.LogInformation($"Created order {orderId}");
return Task.FromResult(new CreateOrderReply {
38
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
}
});
OrderId = orderId
public override async Task GetOrderStatus(GetOrderStatusRequest request,
IServerStreamWriter<GetOrderStatusResponse> responseStream, ServerCallContext
context)
{
await responseStream.WriteAsync(
new GetOrderStatusResponse { Status = "Created" });
await Task.Delay(500);
await responseStream.WriteAsync(
new GetOrderStatusResponse { Status = "Validated" });
await Task.Delay(1000);
await responseStream.WriteAsync(
new GetOrderStatusResponse { Status = "Dispatched" });
}
}
The implementation of CreateOrder is fairly similar to the previous Greeter one. The GetOrderStatus
one is a bit more interesting. Being a server streaming method, it receives a ResponseStream parameter
that lets you send as many response messages back to the client as needed. The implementation above
simulates a real service by sending some messages after certain delays.
2.3 Hosting the service with ASP.NET Core
The only bit we need to modify from the generated project in order to host our new OrdersPlacement
service is to replace the endpoints.MapGrpcService<GreeterService>() in the Startup class with
endpoints.MapGrpcService<OrderPlacementService>().
The template is already adding all the gRPC infrastructure needed to host the service in ASP.NET via
the existing services.AddGrpc() call. You should be able to start the server with the dotnet run
command:
Figure 5, running the first gRPC service
www.dotnetcurry.com/magazine
39
The server will start listening on port 5001 using HTTPS (and 5000 using HTTP) as per the default settings.
Time to switch our focus to the client side.
3. CREATING A GRPC CLIENT
While there is no template to generate a client, the new Grpc.Net.Client and Grpc.Net.ClientFactory
packages combined with gRPC tooling makes it easy to create the code needed to invoke gRPC service
methods.
3.1 Using Grpc.Net.Client in a Console project
First, let’s create a simple console application that lets us invoke the methods of the OrdersPlacement
service we defined in the previous section. Start by adding a new project named Client to your solution,
using the .NET Core Console Application template.
Once generated, install the following NuGet packages: Grpc.Tools, Grpc.Net.Client and Google.Protobuf. (If
using the GUI in Visual Studio, remember to tick the “Include prerelease” checkbox)
Next, manually edit the Client.csproj project file. First update the Grpc.Tools reference with the
PrivateAssets attribute, so .NET knows not to include this library in the generated output:
<PackageReference Include="Grpc.Tools" Version="2.23.0" PrivateAssets="All" />
Then, include a new ItemGroup with a Protobuf item that references the orders.proto file defined in the
previous section:
<ItemGroup>
<Protobuf Include="..\Protos\orders.proto" GrpcServices="Client" />
</ItemGroup>
Notice the GrpcServices attribute telling Grpc.Tools that this time we need client stub code instead of
service base classes!
Once you are done, rebuild the project. You should now be able to use the generated client code under the
Orders namespace. Let’s write the code needed to instantiate a client of our service:
var channel = GrpcChannel.ForAddress(("https://localhost:5001");
var ordersClient = new OrderPlacement.OrderPlacementClient(channel);
We simply instantiate a GrpcChannel using the URL of our service using the factory method
provided by the GrpcClient.Client package, and use it to create the instance of the generated class
OrderPlacementClient.
You can then use the ordersClient instance to invoke the CreateOrder method:
40
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Console.WriteLine("Welcome to the gRPC client");
var reply = await ordersClient.CreateOrderAsync(new CreateOrderRequest
{
ProductId = "ABC1234",
Quantity = 1,
Address = "Mock Address"
});
Console.WriteLine($"Created order: {reply.OrderId}");
Now build and run the project while your server is also running. You should see your first client-server
interaction using gRPC.
Figure 6, client invoking a service method
Invoking the server streaming method is equally simple. The generated GetOrderStatus method of the
client stub returns an AsyncServerStreamingCall<GetOrderStatusResponse>. This is an object with
an async iterable that lets you process every message received by the server:
using (var statusReplies = ordersClient.GetOrderStatus(new GetOrderStatusRequest {
OrderId = reply.OrderId }))
{
while (await statusReplies.ResponseStream.MoveNext())
{
var statusReply = statusReplies.ResponseStream.Current.Status;
Console.WriteLine($"Order status: {statusReply}");
}
}
If you build and run the client again, you should see the streamed messages:
www.dotnetcurry.com/magazine
41
Figure 7, invoking the server streaming method
While this does not cover the four method styles available, it should give you enough information to
explore the remaining two on your own. You can also explore the gRPC examples in the official grpc-dotnet
repo, covering the different RPC styles and more.
3.2 Using Grpc.Net.ClientFactory to communicate between
services
We have seen how the Grpc.Net.Client package allows you to create a client using the well-known
HttpClient class. This might be enough for a sample console application like the one we just created.
However, in a more complex application you would rather use HttpClientFactory to manage your
HttpClient instances.
The good news is that there is a second package Grpc.Net.ClientFactory which contains the necessary
classes to manage gRPC clients using HttpClientFactory. We will take a look by exploring a not so
uncommon scenario, a service that invokes another service. Let’s imagine for a second that our Orders
service needs to communicate with another service, Shippings, when creating an Order.
3.2.1 Adding a second gRPC service
Begin by adding a new shippings.proto file to the Protos folder of the solution. This should look familiar by
now; it is another sample service exposing a traditional Unary RPC method.
syntax = "proto3";
option csharp_namespace = "Shippings";
package Shippings;
42
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
service ProductShipment {
rpc SendOrder (SendOrderRequest) returns (SendOrderReply) {}
}
message SendOrderRequest {
string productId = 1;
int32 quantity = 2;
string address = 3;
}
message SendOrderReply {
}
The only interesting bit is the empty reply message. It is expected for services to always return a message,
even if empty. This is just an idiom in gRPC, it will help with versioning if/when fields are added to the
response.
Now repeat the steps in section 2 of the article to add a new Shippings project to the solution that
implements the service defined by this proto file. Update its launchSettings.json so this service gets started
at https://localhost:5003 instead of its 5001 default.
If you have any trouble, check the source code on GitHub.
3.2.2 Registering a client with the HttpClientFactory
We will now update the Orders service so it invokes the SendOrder method of the Shippings service
as part of its CreateOrder method. Update the Orders.csproj file to include a new Protobuf item
referencing the shippings.proto file we just created. Unlike the existing server reference, this will be a
client reference so Grpc.Tools generates the necessary client classes:
<ItemGroup>
<Protobuf Include="..\Protos\orders.proto" GrpcServices="Server" />
<Protobuf Include="..\Protos\shippings.proto" GrpcServices="Client" />
</ItemGroup>
Save and rebuild the project. Next install the Grpc.Net.ClientFactory NuGet package. Once installed, you
will be able to use the services.AddGrpcClient extension method to register the client. Update the
ConfigureServices method of the startup class as follows:
services.AddGrpc();
services
.AddGrpcClient<ProductShipment.ProductShipmentClient>(opts =>
{
opts.Address = new Uri("https://localhost:5003");
});
This registers a transient instance of the ProductShipmentClient, where its underlying HttpClient is
automatically managed for us. Since it is registered as a service in the DI container, we can easily request
an instance in the constructor of the existing OrdersPlacement service:
private readonly ILogger<OrderPlacementService> _logger;
private readonly ProductShipment.ProductShipmentClient _shippings;
public OrderPlacementService(ILogger<OrderPlacementService> logger,
ProductShipment.ProductShipmentClient shippings)
www.dotnetcurry.com/magazine
43
{
}
_logger = logger;
_shippings = shippings;
Updating the existing CreateOrder method implementation so it invokes the new service is pretty easy as
well:
public override async Task<CreateOrderReply> CreateOrder(CreateOrderRequest
request, ServerCallContext context)
{
var orderId = Guid.NewGuid().ToString();
await this._shippings.SendOrderAsync(new SendOrderRequest
{
ProductId = request.ProductId,
Quantity = request.Quantity,
Address = request.Address
});
this._logger.LogInformation($"Created order {orderId}");
return new CreateOrderReply {
OrderId = orderId
};
}
Once you are done with the changes, start each service on its own console and run the client on a third:
Figure 8, trying our client and the 2 services
This concludes the basics for both implementing and calling gRPC services in .NET.
We have just scratched the surface of the different method styles available. For more information on
Authentication, Deadlines, Cancellation, etc. check the Microsoft docs and the gRPC examples in the official
grpc-dotnet repo.
4. CROSS-LANGUAGE GRPC CLIENT AND
SERVICE
One of the key aspects of gRPC is its cross-language nature. Given a proto file; client and service can be
44
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
generated using any combination of the supported languages. Let’s explore this by creating a gRPC service
using Node.js, a new Products service we can use to get the details of a given product.
4.1 Creating a gRPC server in Node
Like we did with the previous Orders and Shippings services, let’s begin by adding a new products.proto file
inside the Protos folder. It will be another straightforward contract with a Unary RPC method:
syntax = "proto3";
option csharp_namespace = "Products";
package products;
service ProductsInventory {
rpc Details(ProductDetailsRequest) returns (ProductDetailsResponse){}
}
message ProductDetailsRequest {
string productId = 1;
}
message ProductDetailsResponse {
string productId = 1;
string name = 2;
string category = 3;
}
Next create a new Products folder inside the solution and navigate into it. We will initialize a new
Node.js project by running the npm init command from this folder. Once finished, we will install the
necessary dependencies to create a gRPC service using Node:
npm install --save grpc @grpc/proto-loader
Once installed, add a new file server.js and update package.json main property as
"main": "server.js". This way we will be able to start the gRPC server running the npm run
command. It is now time to implement the service.
The actual method implementation is a fairly simple function:
const serviceImplementation = {
Details(call, callback) {
const { productId } = call.request;
console.log('Sending details for:', productId);
callback(null, {productId, name: 'mockName', category: 'mockCategory'});
}
};
Now we just need a bit of plumbing code using the installed grpc dependencies in order to get an HTTP/2
server started that implements the service defined by the proto file and the concrete implementation:
const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
// Implement service
const serviceImplementation = { ... };
www.dotnetcurry.com/magazine
45
// Load proto file
const proto = grpc.loadPackageDefinition(
protoLoader.loadSync('../Protos/products.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
})
);
// Define server using proto file and concrete implementation
const server = new grpc.Server();
server.addService(proto.products.ProductsInventory.service, { Details: getdetails
});
// get the server started
server.bind('0.0.0.0:5004', grpc.ServerCredentials.createInsecure());
server.start();
Even though we are using a different language, the code listing we saw should result pretty familiar after
the .NET services. Developers are responsible for writing the service implementation. Standard gRPC
libraries are then used to load the proto file and create a server able to handle the requests, by routing
them to the appropriated method of the implementation and handling the serialization/deserialization of
messages.
There is however an important difference!
Note the second argument grpc.ServerCredentials.createInsecure() to the server.bind
function. We are starting the server without using TLS, which means traffic won’t be encrypted across the
wire. While this is fine for development, it would be a critical risk in production. We will come back to this
topic in Section 5.
4.2 Communicate with the Node service
Now that we have a new service, let’s update our console application to generate the necessary client
stub code to invoke its methods. Edit the Client.csproj project file adding a new Protobuf element that
references the new products.proto file:
<Protobuf Include="..\Protos\products.proto" GrpcServices="Client" />
Once you save and rebuild the project, let’s add the necessary code to our client application in order to
invoke the service. Feel free to get a bit creative for your Console application to provide some interface that
lets you invoke either the Products or Order services.
The basic code to invoke the Details method of the products service is as follows:
var channel = GrpcChannel.ForAddress("http://localhost:5004");
var productsClient = new ProductsInventory.ProductsInventoryClient(channel);
var reply = await productsClient.DetailsAsync(new ProductDetailsRequest { ProductId
= "ABC1234" });
Console.WriteLine($"Product details received. Name={reply.Name}, Category={reply.
Category}");
46
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
This is very similar to the code that sends a request to the Orders service. Note however that since our
server does not have TLS enabled, we need to define the URL using the http:// protocol instead of
https://. If you now try to run the client and send a request to the server, you will receive the following
exception:
Figure 9, exception trying to invoke a service without TLS
This is because when using the HTTP/2 protocol, the HttpClient used by the service client will enforce
TLS. Since the Node server does not have TLS enabled, the connection cannot be established. In order to
allow this during development, we need to use the following AppContext switch as advised in the official
docs:
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",
true);
Once we make that change, we are able to invoke the Node service from our .NET client:
Figure 10, invoking the Node.js gRPC service from the .NET client
As you can see, it does not matter on which language (or platform for that matter) are the client and server
www.dotnetcurry.com/magazine
47
running. As long as both adhere to the contract defined in the proto files, gRPC will make the transport/
language choice transparent for the other side.
The final section of the article will look at TLS/SSL support in greater detail.
5. UNDERSTANDING TLS/SSL WITH GRPC
5.1 Server credentials for TLS/SSL
gRPC is designed with TLS/SSL (Transport Layer Security/Secure Sockets Layer) support in mind in order
to encrypt the traffic sent through the wire. This requires the gRPC servers to be configured with valid SSL
credentials.
If you have paid attention throughout the article, you might have noticed that both services implemented
in .NET and hosted in ASP.NET Core applications (Orders and Shippings) used TLS/SSL out of the box
without us having to do anything. Whereas in the case of the Node.js app, we used unsecure connections
without TLS/SSL since we didn’t supply valid SSL credentials.
5.1.1 ASP.NET Core development certificates
When hosting a gRPC server within an ASP.NET Core application using Kestrel, we get the same default
TLS/SSL features for free as with any other ASP.NET Core application. This includes out of the box
development certificates and default HTTPS support in project templates, as per the official docs.
By default, development certificates are installed in ${APPDATA}/ASP.NET/Https folder (windows)
or the ${HOME}/.aspnet/https folder (mac and linux). There is even a CLI utility named dotnet devcertsthat comes with .NET Core and lets you manage the development certificate,. Check out this article
from Scott Hanselman from more information.
Essentially, this means when implementing gRPC services with ASP.NET Core, the SSL certificate is
automatically managed for us and TLS/SSL is enabled even in our local development environments.
5.1.2 Manually generating development certificates
You might find yourself working on a platform that does not provide you with development certificates by
default, like when we created the Node.js server.
In those cases, you will need to generate yourself a self-signed set of SSL credentials that you can then
provide to your server during startup. Using openssl, it is relatively easy to create a script that will generate
a new CA root certificate, and a public/private key pair for the server. (If you are in Windows, installing git and
the git bash is the easiest way to get it). Such a script will look like this:
openssl genrsa -passout pass:1234 -des3 -out ca.key 4096
openssl req -passin pass:1234 -new -x509 -days 365 -key ca.key -out ca.crt -subj
"/C=CL/ST=RM/L=Santiago/O=Test/OU=Test/CN=ca"
48
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
openssl genrsa -passout pass:1234 -des3 -out server.key 4096
openssl req -passin pass:1234 -new -key server.key -out server.csr -subj
ST=RM/L=Santiago/O=Test/OU=Server/CN=localhost"
"/C=CL/
openssl x509 -req -passin pass:1234 -days 365 -in server.csr -CA ca.crt -Cakey
ca.key -set_serial 01 -out server.crt
openssl rsa -passin pass:1234 -in server.key -out server.key
The script first generates a self-signed CA(Certificate Authority) root, then generates the public (server.crt)
and private key (server.key) parts of a X509 certificate signed by that CA. Check the GitHub repo for full
mac/linux and windows scripts.
Once the necessary certificates are generated, you can now update the code starting the Node.js gRPC
service to load the SSL credentials from these files. That is, we will replace the line:
server.bind('0.0.0.0:5004', grpc.ServerCredentials.createInsecure());
..with:
const fs = require('fs');
...
const credentials = grpc.ServerCredentials.createSsl(
fs.readFileSync('./certs/ca.crt'),
[{
cert_chain: fs.readFileSync('./certs/server.crt'),
private_key: fs.readFileSync('./certs/server.key')
}],
/*checkClientCertificate*/ false);
server.bind(SERVER_ADDRESS, credentials);
If you restart the Node service and update the address in the console client to https://localhost:5004, you
will realize something is still not working:
Figure 11, the self-signed certificate for the Node service is not trusted
www.dotnetcurry.com/magazine
49
This is because we have self-signed the certificate, and it is not trusted by our machine. We could add it to
our trusted store, but we could also disable the validation of the certificate in development environments.
We can provide our own HttpClient instance used by the GrpcChannel through the optional
GrpcChannelOptions parameter:
var httpHandler = new HttpClientHandler();
httpHandler.ServerCertificateCustomValidationCallback =
(message, cert, chain, errors) => true;
var httpClient = new HttpClient(httpHandler);
var channel = GrpcChannel.ForAddress("http://localhost:5004", new
GrpcChannelOptions { HttpClient = httpClient });
var productsClient = new ProductsInventory.ProductsInventoryClient(channel);
And this way we can use SSL/TLS with our Node.js service.
5.2 Mutual authentication with client provided credentials
gRPC services can be configured to require client certificates within the requests, providing mutual
authentication between client and server.
Begin by updating the previous certificate generation script so it also produces client certificates using the
same CA certificate:
openssl genrsa -passout pass:1234 -des3 -out client.key 4096
openssl req -passin pass:1234 -new -key client.key -out client.csr -subj
ST=RM/L=Santiago/O=Test/OU=Client/CN=localhost"
"/C=CL/
openssl x509 -passin pass:1234 -req -days 365 -in client.csr -CA ca.crt -CAkey
ca.key -set_serial 01 -out client.crt
openssl rsa -passin pass:1234 -in client.key -out client.key
openssl pkcs12 -export -password pass:1234 -out client.pfx -inkey client.key -in
client.crt
Now edit the Client.csproj project file to copy the generated certificate files to the output folder:
<ItemGroup>
<None Include="..\Products\certs\*.*" LinkBase="ProductCerts">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
Note that the folder from which you Include them will depend on where your script creates the certificate files. In
my case, it was a folder named certs inside the Products service.
Now we need to provide the generated client.pfx certificate when connecting with the Products GRPC
server. In order to do so, we just need to load it as an X509Certificate2 and tell the HttpClientHandler
used by the GrpcChannel to supply said certificate as the client credentials.
50
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
This might sound complicated, but it doesn’t change much the code that initialized the Products client
instance:
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
var certificate = new X509Certificate2(
Path.Combine(basePath, "ProductCerts", "client.pfx"), "1234"))
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
var httpClient = new HttpClient(handler);
var channel = GrpcChannel.ForAddress(serverUrl, new GrpcChannelOptions { HttpClient
= httpClient });
return new ProductsInventory.ProductsInventoryClient(channel);
Note that you need to supply the same password to the X509Certificate2 constructor that you used to
generate the client.pfx file. For the purposes of the article we are hardcoding it as 1234!
Once the client is updated to provide the credentials, you can update the checkClientCertificate
parameter of the Node server startup and verify that the client provides a valid certificate as well.
5.3 Using development certificates with Docker for local
development
To finish the article, let’s take a quick look at the extra challenges that Docker presents in regards to
enabling SSL/TLS during local development.
To begin with, the ASP.NET Core development certificates are not automatically shared with the containers.
In order to do so, we first need to export the certificate as a pfx file as per the commands described in this
official samples. For example, in Windows you can run:
dotnet dev-certs https -ep ${APPDATA}/ASP.NET/Https/aspnetapp.pfx -p mypassword
Exporting the certificate is just the beginning. When running your containers, you need to make this
certificate available inside the container. For example, when using docker-compose you can mount the
folder in which the pfx file was generated as a volume:
volumes:
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
Then setup the environment variables to override the certificate location and its password:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+;http://+
- ASPNETCORE_HTTPS_PORT=5001
- ASPNETCORE_Kestrel__Certificates__Default__Password=mypassword
- ASPNETCORE_Kestrel__Certificates__Default__Path=/root/.aspnet/https/aspnetapp.
pfx
www.dotnetcurry.com/magazine
51
This should let you run your ASP.NET Core gRPC services using docker with TLS/SSL enabled.
Figure 12, using docker for local development with SSL enabled
One last caveat though! The approach we just saw works as long as you map the container ports to your
localhost, since the self-signed SSL certificates are created for localhost. However, when two containers
communicate with each other (as in the Orders container sending a request to the Shippings service),
they will use the name of the container as the host in the URL (i.e., the Orders service will send a request
to shippings:443 rather than localhost:5003). We need to bypass the host validation or the SSL
connection will fail.
How the validation is disabled depends on how the client is created. When using the
Grpc.Net.Client, you can supply an HttpClientHandler with a void implementation of its
ServerCertificateCustomValidationCallback. We have already seen this in section 5.1.2 in order to
accept the self-signed certificate created for the Node server.
When using the Grpc.Net.ClientFactory, there is a similar approach that lets you configure the
HttpClientHandler after the call to services.AddGrpcClient:
services
.AddGrpcClient<ProductShipment.ProductShipmentClient>(opts =>
{
opts.Address = new Uri(shippings_url);
}).ConfigurePrimaryHttpMessageHandler(() => {
return new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
true
};
});
The second approach would be needed if you want to run both the Orders and Shippings services inside
docker.
52
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Figure 13, services communicating with each other using SSL inside docker
Before we finish, let me remark that you only want to disable this validation for local development
purposes. Make sure these certificates and disabled validations do not end up being deployed in
production!
Conclusion
A first-class support of gRPC in the latest ASP.NET Core 3.0 release is great news for .NET developers. They
will get easier than ever access to a framework that provides efficient, secure and cross-language/platform
Remote Procedure Calls between servers.
While it has a major drawback in the lack of browser support, there is no shortage of scenarios in which it
can shine or at least become a worthy addition to your toolset. Building Microservices, native mobile apps
or Internet of Things (IoT) are just a few of the examples where gRPC would be a good fit.
Download the entire source code from GitHub at
bit.ly/dncm44-grpc
Daniel Jimenez Garcia
Author
Daniel Jimenez Garcia is a passionate software developer with 10+ years of experience. He started as
a Microsoft developer and learned to love C# in general and ASP MVC in particular. In the latter half
of his career he worked on a broader set of technologies and platforms while these days is particularly
interested in .Net Core and Node.js. He is always looking for better practices and can be seen answering
questions on Stack Overflow.
Thanks to Dobromir Nikolov for reviewing this article.
www.dotnetcurry.com/magazine
53
VISUAL STUDIO
Dobromir Nikolov
STREAMLINING YOUR
VISUAL STUDIO
PROJECT SETUP
Creating and distributing Visual Studio
templates is hard. You need to get familiar
with custom XML formats, the VSIX project
type, and Visual Studio’s occasionally eccentric
behavior.
Don’t waste time with that.
Learn how you can
instantly extract a readyto-go template out of your
existing solution using
solution-snapshotter.
Creating custom multi-project templates for Visual Studio is hard. There’s
lots of tricky stuff you need to do in order for your template to have a
decent physical and solution structure. I wrote a tool to help you with that.
Enter solution-snapshotter - a tool that automatically exports a given solution
as a Visual Studio template. It takes care of preserving your solution and
folder structure, and also keeps any extra non-project files you may have
such as configurations, ruleset files, etc. The generated template will be
packaged in a Visual Studio extension for easy installation and distribution.
Go straight to the repo to see it in action or read the rest of the article for a
more detailed explanation on what problems does it solve and how you can
use it.
Solution Snapshotter – Visual Studio tool
Have you ever found yourself setting up the same project structure, using the same framework, the same
ORM, the same logging library - over and over again?
I know I have.
This can potentially take up days until you get every tiny detail right.
When you’re using .NET with Visual Studio and you find yourself setting up projects often, the logical
solution would be to automate this process through a Visual Studio template. Like one of these:
www.dotnetcurry.com/magazine
55
To create such a template, there’s an Export Template button under the Project menu (on the top) inside
Visual Studio.
It creates a .zip file that you have to place inside the “%USER%/Documents/Visual Studio/Templates/
ProjectTemplates” folder. After doing that, you’ll have your project available as a template.
The problem with Visual Studio Export
Visual Studio export works only for single assemblies that don’t reference anything else, but you’d rarely
see a setup as simple as that.
Most modern projects adopt a multi-assembly approach, with solution structures often looking similar to
this:
And folder structures looking similar to this:
56
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
You can have templates like that. They are called multi-project templates.
The problem is that when you attempt to create one, things get unnecessarily complicated.
Through the rest of the article, we’ll go over the creation of such templates and the numerous caveats that
appear during the process.
Creating multi-project templates
Say you have the solution similar to the one we just saw and you have already set up various tools such as
XUnit, EntityFramework, Swagger, StyleCop, authentication/authorization, etc. You would like to export the
solution as a template so you can reuse the setup for other projects.
It shouldn’t be a big deal, right? Let’s check for ourselves.
Note: This particular project setup is available on the VS Marketplace. It is automatically generated from this
source project using solution-snapshotter - the tool we’ll be introducing.
Getting started
Note: The content that follows is not meant to be a tutorial on how to create multi-project templates. Don’t
worry if you feel lost at any point. For the more curious among you, I’ll be providing links where you can get
additional info on things that we’ll simply skim over. The main points are: there’s lots of tricky stuff to do; you can
avoid doing it by using the tool which we’ll introduce later.
The documentation for creating multi-project templates recommends us to use the built-in Export
Template functionality for each project in our solution. We should then combine the exported project
templates into a single, multi-project template using a special .vstemplate format.
After doing this for the setup that we introduced, we get to a folder looking like this:
Each MyProject.* folder is a project template that was exported by Visual Studio. What we did is simply
place them into a single directory. We also wrote the MyProjectTemplate.vstemplate file. It is what unites
everything into a single, multi-project template. Its contents are the following:
www.dotnetcurry.com/magazine
57
After zipping everything and placing it into Visual Studio’s ProjectTemplates folder, we have our template
available.
Let’s use it to create a project called TestTemplateProject.
58
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Structure-wise, it looks okay. However, if we try to build it, we’ll quickly find out that the references are all
broken!
You see, our initial project had a clean and tidy folder structure, like this:
However, when using the built-in Export Template functionality, Visual Studio ignores all that and just
flattens the structure like so:
It doesn’t care to fix the reference paths inside the .csproj files that were meant for the original folder
structure, thus they get broken.
Through the error messages, we can also see that the reference names haven’t been updated. Even though
we’ve named our new project TestTemplateProject, the references in the .csproj files are all looking for
assemblies named MyProject.*, which was the original project name.
Caveat 1: Custom physical folder structure
The bad news is, the multi-project template format does not support custom physical folder structures.
www.dotnetcurry.com/magazine
59
The good news is, we can work around that by packaging our template into a Visual Studio extension
(VSIX). This will allow us to plug in during the project creation phase and execute custom logic using the
IWizard interface.
Note: I won’t get too much into how you can package your template or use the IWizard interface as that will
bloat the article significantly. If it interests you, there’s a tutorial on packaging in the docs. After getting through
it, read about using wizards. Then, you’ll need to get familiar with the _Solution interface and its derivatives Solution2, Solution3, and Solution4. Overall, working with these is clumsy. You often need to resort to explicit
typecasts and try-catch blocks. If you’re looking into it, these extension methods will come in handy.
After packaging your template in a Visual Studio extension project, you’ll have something similar to this:
You can build this project to get a .vsix file, which you can run to install the extension to Visual Studio
instances of your choice.
After installing the built .vsix, the template will become available
in Visual Studio.
Again, there are quite a few steps to get to this point. We will not be digging further into those. Our goal is
to just skip over to the largest issues that arise during the template creation process. This is so we can then
clearly see how and why those issues are solved using solution-snapshotter - the tool we’ll introduce at the
end of the article.
If you would like to learn more about the manual process, the docs are a decent starting point.
We’re continuing the article under the following assumptions: we’ve successfully packaged our template
into a VSIX; we’ve successfully implemented custom logic that creates a correct folder structure and
rearranges our projects correspondingly; we’ve successfully fixed the broken reference paths; we’ve
successfully installed the VSIX to Visual Studio.
Our newly packaged template
Here is how our template looks after installing.
Let’s create a new project out of it called TestVSIXTemplate.
60
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Looks pretty nice, we can also see that the generated folder structure is correct.
But again, even though we’ve fixed the folder structure and references… it doesn’t build.
Caveat 2: Extra files and folders
We can see from the error message that the stylecop.json file is missing. In the initial project, this file was
placed in a separate folder called configuration. It wasn’t directly referenced by any .csproj, but instead was
added as a link.
The configuration folder is however, missing. This is because when we initially exported each project using
Export Template, any extra files were lost. The .vstemplate format only allows you to include files that are
directly referenced by the .csproj files.
Again, there’s a workaround.
Since we’ve packaged our template into a Visual Studio extension and Visual Studio extensions are simply
.NET assemblies, we can include any extra files we like by either embedding them or including them as
assets. They can be then unpacked during the folder structure creation using custom logic placed inside the
IWizard interface implementation.
Not a very pleasant development experience.
www.dotnetcurry.com/magazine
61
Caveat 3: Maintenance
Even if we do all of the above correctly, we still have to maintain the template. That brings us to another
problem.
The template we have is no longer a runnable project, but instead is a bunch of tokenized source files
similar to this one.
This means that any changes we make, we can’t compile and test. Testing is done by:
1.
2.
3.
4.
5.
Updating the template (means digging around broken files)
Updating the VSIX
Running the VSIX inside an experimental Visual Studio instance
Creating a new project with the template
Verifying everything works as expected
Great if you want to spend 30 minutes just updating a NuGet package and moving a few classes!!
Forget all about this - use solution-snapshotter
All of the aforementioned problems are reaI. I encountered them while building and maintaining the Dev
Adventures template.
And it never felt right. It always seemed to me that all of this VSIX mumbo jumbo should happen
automatically inside a CI/CD pipeline.
This led me to write a tool that automatically does everything for you. It takes an initial project source and
62
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
gives you a ready to go VSIX project that has your project packaged as a template. And it takes care of all
the mentioned issues!
Your physical folder structure, references and extra files will be preserved without writing a single line of
code. Just run solution-snapshotter.exe and enjoy the magic.
See the Dev Adventures template extension. It is now 100% automatically generated and published to the
VS Marketplace. You can check out the source repository here. Note the input.config file, that’s all you really
need.
solution-snapshotter is open-source and written in F#. You can visit the GitHub repo by going to this link.
The usage is very simple, the readme should be enough to get you started.
How can it help, you may ask? Let’s see.
For the setup that we introduced in the beginning..
..we can simply call solution-snapshotter.exe. (see the Minimal Usage section for a download link and a
command you can test with)
..and receive a ready-to-go VSIX project!
www.dotnetcurry.com/magazine
63
We can build it.
And receive our .vsix installer.
After installing, the template will become available inside Visual Studio!
64
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
We can use it to create a new project.
..and have our setup ready in moments!
And of course, with all references being valid!
www.dotnetcurry.com/magazine
65
If you’re working for a service company, use solution-snapshotter to create and maintain standard
templates for different project types.
If you’re doing microservices, use it to create common templates for services and shared libraries.
Or if you just finished setting up a great project, use it to share it with the world by extracting a template
and shipping it to the VS Marketplace.
Again, solution-snapshotter is open-source! All contributions and feedback are most welcome. Don’t
hesitate to open an issue if there’s anything you think can be improved.
Enjoy your headache-free templates!
Download the entire source code from GitHub at
bit.ly/dncm44-snapshotter
Dobromir Nikolov
Author
Dobromir Nikolov is a software developer working mainly with Microsoft technologies, with his
specialty being enterprise web applications and services. Very driven towards constantly improving
the development process, he is an avid supporter of functional programming and test-driven
development. In his spare time, you’ll find him tinkering with Haskell, building some project
on GitHub (https://github.com/dnikolovv), or occasionally talking in front of the local tech
community.
Thanks to Yacoub Massad for reviewing this article.
66
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
www.dotnetcurry.com/magazine
67
DEVOPS
Subodh Sohoni
DEVOPS
TIMELINE
C
oncept of Agile teams is a passé.
Many organizations do not consider to
build a DevOps teams. DevOps cannot be
DevOps teams is the new ‘in thing’. It is simple
‘implemented’. DevOps is the way teams
to understand that DevOps is just extending the
are designed and it becomes a part of the
concepts and scope of Agile to the operations
mindset of the team. These teams consist of
and testing functions in the organization, if they
representation from business, architecture,
are not already present.
development, operations, testing and any other
function that will make possible the agile
promise of ‘early and frequent delivery of value
to the customer’.
When I try and capture the context of DevOps
in one diagram, I usually end up with the one
shown in Figure 1 (see next page). Although it
shows all the activities in an order that is easy
to understand, it does not depict the correct
picture of how practically a team may execute
those activities.
Figure 1: Scope of DevOps
Issue at hand
When so many disciplines are going to work together, it becomes very difficult to imagine the coherence of
all activities of all of those disciplines. It is even more difficult to put those on the same timeline without
conflicting with the others, and at the same time, supporting others to improve the productivity of the
entire team.
The Scrum concept of Sprint gives one way of putting many activities in the timebox of sprint and provides
a guideline for certain timeline. These activities start from a development team starting a sprint, with
sprint planning, and ends with the same team doing a retrospection to inspect and adapt to improve.
These activities are fairly linear and not many are on a parallel or diverging track. May be, backlog
grooming is the only parallel track that can be thought of. Another observation is that all these activities
are related to managing the team, requirements and efforts involved. None of them are for agile practices
that focus on development and engineering.
I can easily list these activities for you, although it is not an exhaustive list:
•
•
•
•
•
•
•
Practices related to version control like branching and merging
Builds with automated triggers like continuous integration
Code review
Provisioning of environments to make them ready for deployment
Deployment of packages to the provisioned environments
Testing the product after it is deployed. These may be functional tests or non-functional tests like
performance testing
Monitoring the health of the product
www.dotnetcurry.com/magazine
69
How to put all these activities in the timebox of sprint and on the same timeline of earlier identified agile
practices, is an issue that many teams fail to address successfully.
What it means is that teams follow agile planning and management, but cannot correlate those to
continuous delivery(CD) using agile development practices. I have observed many teams finding a solution
of their own for this issue. Those solutions are applicable to their situations and scenarios.
This article is an effort to showcase the best of such practices that I have observed on one timeline, if they
are generically applicable.
Let us first define the context of the problem. We have a timebox of sprint, which is fixed for a team
anywhere between two to four weeks. In this timebox, there are predefined activities:
•
•
•
•
•
Sprint planning
Daily Scrum
Development, which may involve architecture changes, design, coding and code quality maintenance
like code analysis, unit testing and code review
Sprint Review
Retrospective
Start of the timeline with known Scrum activities
I am going to put it on a timeline of the sprint for better visualization.
Figure 2: Scrum events on timeline
Now, on this timeline, I would like to put the other variables that I have listed earlier. I will begin with
practices of version control - branching and merging.
Version Control – Align branching and
merging in iteration (sprint)
Feature Branches
For a team that is developing new software, I recommend using GitFlow branching strategy which is based
upon features.
At the beginning of the sprint, for every feature that is to be developed, a branch is created. These branches
are created from a long running branch, called Dev branch as a convention. Some feature branches are
long running. They span multiple sprints and are continued from an earlier sprint. All the code that is to be
deployed in the sprint will be developed in feature branches, and once developed, they will be merged in
the Dev branch. This merge will be done after a code review, say by a pull request on the feature branch.
70
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
It is also suggested to have synchronization of code by forward and backward merge. Frequency of such
synchronization should not exceed two days to reduce the chances of large conflicts getting created.
I am not showing those synchronization merges to avoid clutter on my diagrams.
Release Branch
A short running Release branch is created from Dev branch once all the feature branches of the sprint are
merged in the Dev branch. Release branch is where integration testing will be done on the code. There are
no direct commits or check-ins in the Dev and Release branch. Development of long running features will
continue in their own branch. After the integration testing and any other non-functional testing, the release
branch is merged in another long running branch, conventionally called as Production or master branch.
Production (master) branch
The production environment receives code from this branch.
Hotfix branches
You can also take care of urgent fixes for critical bugs in the production code by creating hotfix branches
from the Production branch. These get merged back in the Production branch as well as in the Dev branch
and in the Release branch, if it is there at the time when the fix was created.
We can view these branches on the timeline as shown in Figure 3.
Figure 3: Branching strategy in iteration
www.dotnetcurry.com/magazine
71
Version Control – Branching and merging
strategy without iterative development
For a team that is maintaining a developed software by doing bug fixes and small enhancements, concept
of the timebox of sprint is not applicable.
We are now in the context of continuous delivery of value to customers without the period of development
of batch of requirements. Every bug or a change request has to be delivered as soon as its code is ready for
delivery. In such conditions, it is necessary to have a different timeline and a branching strategy. There is no
start and end of a timebox, like a sprint, and each bug or a change request will have its own branch from
the Dev branch. Hotfix branches will remain as in the earlier model.
Note: Although I am considering and showing this model here, in the subsequent topics and diagrams, I have
considered the timebox model only, since that is more prevalent.
Figure 4: Branching strategy without iterations
Build Strategy on the timeline
Now, let us think about the builds. A common build strategy is to have a build pipeline defined for each of
the branch.
•
72
Trigger for build of the feature branches, as well as for hotfix branches, should be Continuous
Integration(CI). This ensures that whenever code is pushed in the branch, it will be built to ensure its
integration with the code that was pushed by other team members in the same branch.
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
•
Triggers for build on Dev and Production branch need to be against the code being merged from other
branches, which again is the trigger of Continuous Integration, but those branches will get built less
often.
•
Trigger for build of Release branch needs to be manual so that it gets built only as often as required.
Figure 5: Build strategy on timeline of iteration
Provisioning and Deployment
Let us now move our attention to the next important agile practice and that is continuous deployment (CD).
This practice is divided in two parts, Provisioning and Deployment.
Provisioning
For the CD to work, we need to ensure that the environments on which the deployment is to be done, is in a
state where deployment is possible.
For example, if a web application is to be deployed, it is necessary that the server that is going to host the
application has the necessary prerequisites like IIS installed and configured. If an earlier build is already
installed on it, then that needs to be removed.
This preparatory work is called provisioning. We need to define environments for deployments for the
purpose of development (smoke testing), testing, UAT, production etc. These environments may need to
be created from scratch or may need to be refreshed every time a new set of software packages are to be
deployed.
www.dotnetcurry.com/magazine
73
The Production environment usually will not change. Other environments may be provisioned initially
once in the sprint, or can be provisioned every time a new build is to be deployed. Deployment of builds of
feature branches and hotfix branches will happen on the Dev environment and that is provisioned every
time the build is to be deployed, just before the deployment is triggered.
For this activity to be idempotent (resulting in same outcome every time, if executed multiple times),
we need to automate it. This is done using either the ARM templates or any other technology that the
platform supports. For more information about it, please go through my article at www.dotnetcurry.com/
visualstudio/1344/vsts-release-management-azure-arm-template. Terraform is a popular software for this
which can target multiple platforms. Writing such a template that provides a desired state of environment
is also called “Infrastructure as Code”. Along with these technologies, PowerShell DSC (Desired State
Configuration) is an important part of it. PowerShell DSC is also used by Azure Automation.
Builds of the Dev branch are deployed on the QA or Test environment for doing integration testing. This
environment is usually provisioned initially once in the sprint. Since it has deployments of earlier features
which may be required for testing the integration, it is not advisable to provision them repeatedly as part of
the deployment process. We can view these on the timeline now.
Deployment
The Application is deployed on the provisioned servers. Automation of this part of the activity is possible
through various tools like Release Management part of Azure Pipelines or open source tool Jenkins (www.
dotnetcurry.com/devops/1477/jenkins-for-devops).
These tools may use protocols like http, https, ftp, WinRM, Bash etc. to copy the packages on target
(provisioned) hosts and to execute the installation scripts. Provisioning and deployment can be executed as
two steps in succession for Dev environment; whereas for test and production environments, they are not
clubbed together.
Figure 6: Provisioning and Deployment Strategy in an iteration
74
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Adding Testing on the timeline
Test types and execution
Testing is an important part of DevOps. It is done at multiple stages. My observations from the processes
followed by many organizations with whom I logically agree, are as follows:
•
Unit testing is done along with development as well as part of the build. This is done usually when the
code is in feature branches but also continues when the code is built in any other branch.
•
Functional testing including regression testing is done on the test environment. Some system
integration testing may also be done in the Test environment before code is branched into Release
branch.
•
Nonfunctional testing like performance testing, penetration testing or system integration testing is
done by a team which may exist outside the DevOps team. It is done on an environment outside the
context of this timeline. This is usually done once the release branch is created.
•
UAT is done on the Release branch in the Test environment
•
Availability testing is done continuously after the application is deployed to production.
This is not an exhaustive list of test types but a list of commonly used test strategies. One should
understand here that although testing is an integral part of DevOps, it is not necessary that all the testing
be done by the DevOps team only. It is observed in many organizations that there are specialized testing
team as part of the QA who do complex testing like nonfunctional testing. These nonfunctional testing
include performance testing, penetration testing and system integration testing. It is not physically and
logistically possible to bring these super-specialists under the DevOps teams. Usually such testing is done
as and when code from Dev branch is branched in to the Release branch.
Test Cases Creation – Planning the testing efforts
Testers not only have to run tests but much of their time is taken in understanding the application changes
and write the test cases for new features. They do start this part of their work as soon as the sprint is
started. It is recommended to write automated tests for unit tests and functional tests. The same automated
tests can be run as part of build (unit tests) and release (post deployment functional tests). The same tests
will also run as part of regression test suite in the subsequent sprints.
www.dotnetcurry.com/magazine
75
Figure 7: Testing on a common timeline
Summary
There are many disciplines that contribute their efforts in DevOps.
In this article, I have shown that their activities and strategies of Agile engineering can be brought on
the same timeline so that each team member playing any role in DevOps team knows what she / he is
supposed to do during the iterations. This strategy is one of the many that can be evolved.
I have pinned my observations from the many organizations I am a consultant for, and taken the best
of them as practices that can be followed by a team. One of the essential things is not to take software
engineering tasks out of context, and timebox agile management through a framework like Scrum.
Subodh Sohoni
Author
Subodh is a consultant and corporate trainer for the subjects of Azure DevOps. He has overall
32+ years of experience in aircraft production, sales, marketing, software development,
training and consulting. He is a Microsoft MVP – Azure DevOps since 2009, and is a Microsoft
Certified Trainer (MCT) since 2003. He is also a Microsoft Certified DevOps Engineer Expert.
He has conducted more than 350 corporate trainings and consulting assignments. He has also
executed trainings and consulting assignments on behalf of Microsoft.
Thanks to Gouri Sohoni for reviewing this article.
76
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
PATTERNS & PRACTICES
Yacoub Massad
FUNCTION
PARAMETERS
IN C# AND THE FLATTENED
SUM TYPE ANTI-PATTERN
In this tutorial, I will discuss function parameters in C#. I
will talk about how function parameters tend to become
unclear as we maintain our programs and how to fix
them.
Function Parameters - Introduction
Function parameters are part of a function’s signature and should be designed in a way to make it easier
for developers to understand a function.
However, badly designed function parameters are everywhere!
In this tutorial, I will go through an example to show how a function that starts with a relatively good
parameter list, ends up with a confusing one as new requirements come and the program is updated.
The sample application
The following is an example of a function’s signature:
public void GenerateReport(int customerId, string outputFilename)
This GenerateReport function exists in some e-commerce solution that allows customers to order
products. Specifically, it exists in a tool that allows people to generate reports about a particular customer.
Currently, the report only contains details about customer orders.
This GenerateReport function takes two parameters as inputs: customerId and outputFilename. The
customerId is used to identify the customer for which to generate the report. The outputFilename is the
location on the disk to store a PDF file that contains the generated report.
I have created a GitHub repository that contains the source code. You can find it here: https://github.com/
ymassad/FunctionParametersExamples
Take a look at the Tool1 project. It is a WPF based tool that looks like the following, when you run it:
The Click event handler of the Generate Report button collects and validates the input and then invokes
the GenerateReport method.
Usually, functions start with a simple parameter list. However, as the program is updated to meet new
requirements, parameter lists become more complicated.
Let’s say that we have a new requirement that says that the report should optionally include details about
customer complaints. That is, the report would include a list of all complaints that the customer has filed in
the system. We decide to add a checkbox to the window to allow users of the tool to specify whether they
want to include such details. Here is how the window in the Tool2 project looks like:
Here is how the signature of the GenerateReport function looks like:
public static void GenerateReport(int customerId, string outputFilename, bool
includeCustomerComplaints)
The includeCustomerComplaints parameter will be used by the GenerateReport function to decide
whether to include the customer complaints in the report.
Let’s say that we have another requirement which says the user should be able to specify whether to
include all customer complaints or just complaints that are opened (not resolved).
Here is how the window in Tool3 looks like:
We added a special checkbox to satisfy the requirement. Notice how this checkbox is disabled in the figure.
It becomes enabled only if the user checks the “Include customer complaints” checkbox. This makes sense
because the value of the second checkbox only makes sense if the first checkbox is checked. Take a look at
MainWindow.xaml in Tool3 to see how this is done.
Also notice how the new checkbox is moved a little to the right. This is to further indicate to the user that
this is somehow a child of the first checkbox.
Here is how the signature of the GenerateReport method in Tool3 looks like:
void GenerateReport(int customerId, string outputFilename, bool
includeCustomerComplaints, bool includeOnlyOpenCustomerComplaints)
We added a boolean parameter called includeOnlyOpenCustomerComplaints to the method which
enables the caller to specify whether to include only open customer complaints or to include all customer
complaints.
Although the UI design makes it clear that the value of “Include only open complaints” makes sense only if
“Include customer complaints” is checked, the signature of GenerateReport method does not make that
clear.
A reader who reads the signature of the GenerateReport method might be confused about which
combinations of parameter values are valid, and which are not.
For example, we know that the following combination does not make sense:
GenerateReport(
customerId: 1,
outputFilename: "c:\\outputfile.pdf",
80
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
includeCustomerComplaints: false,
includeOnlyOpenCustomerComplaints: true);
Since includeCustomerComplaints is false, the value of includeOnlyOpenCustomerComplaints is
simply ignored.
I will talk about a fix later.
For now, let’s look at another requirement: the ability to include only a summary of the customer
complaints. That is, instead of including a list of all complaints in the report, a summary will be included
that contains only:
•
•
•
•
•
•
•
•
•
The number of all complaints filed by the customer.
The number of open complaints.
The number of closed complaints.
The number of open complaints about shipment times.
The number of open complaints about product quality.
The number of open complaints about customer service.
The number of closed complaints about shipment times.
The number of closed complaints about product quality.
The number of closed complaints about customer service.
Before looking at the UI of Tool4, let’s first look at the signature of the GenerateReport method:
void GenerateReport(
int customerId,
string outputFilename,
bool includeCustomerComplaints,
bool includeOnlyOpenCustomerComplaints,
bool includeOnlyASummaryOfCustomerComplaints)
Please tell me if you understand the parameters of this method!
Which combinations are valid, and which are not? Maybe based on the descriptions of the requirements
I talked about so far, you will be able to know. But if you don’t already know about the requirements, the
signature of this method provides little help.
Let’s look at the UI in Tool4 now:
www.dotnetcurry.com/magazine
81
This is much better! We are used to radio buttons.
We know that we can select exactly one of these three options:
1. Do not include customer complaints
2. Include customer complaints
3. Include only a summary of customer complaints
We also know that if we select “Include customer complaints”, we can also choose one of the two options:
1. To include only open complaints.
2. To include all complaints, not just opened ones.
We know these options by just looking at the UI!
Now imagine that someone designed the UI of Tool4 like this:
Would users be able to understand the options displayed in the UI? Would such UI design pass customer
testing of the software?
Although you might have seen bad UI designs such as this one, I bet you have seen it fewer times
compared to the code in the GenerateReport method of Tool4.
Before discussing why this is the case and suggesting a solution, let’s discuss a new requirement first:
The user should be allowed to specify that they want only information relevant to open complaints when
generating the summary report. That is, if such an option is selected, then only the following details are
generated in the complaints summary section of the report:
•
•
•
•
The number of open complaints.
The number of open complaints about shipment times.
The number of open complaints about product quality.
The number of open complaints about customer service.
Here is how the UI looks like in Tool5:
82
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
The options in the UI looks understandable to me. I hope you think the same. Now, let’s look at the
signature of the GenerateReport method:
void GenerateReport(
int customerId,
string outputFilename,
bool includeCustomerComplaints,
bool includeOnlyOpenCustomerComplaints,
bool includeOnlyASummaryOfCustomerComplaints)
Nothing changes from Tool4. Why?
Because when writing code for the new feature, we decide that it is convenient to reuse the
includeOnlyOpenCustomerComplaints parameter. That is, this parameter will hold the value true if
any of the two checkboxes in the UI are checked.
We decided this because these two checkboxes are somewhat the same. They both instruct the
GenerateReport method to consider only open complaints; whether when including a list of complaints,
or just a summary. Also, it could be the case that this parameter is passed to another function, say
GetAllComplaintsDataViaWebService that obtains complaints data from some web service. Such data
will be used in the two cases; when we just want a summary of complaints, or when we want the whole list
of complaints.
I am providing following excerpt from a possible implementation of the GenerateReport function to
explain this point:
void GenerateReport(
int customerId,
string outputFilename,
bool includeCustomerComplaints,
bool includeOnlyOpenCustomerComplaints,
bool includeOnlyASummaryOfCustomerComplaints)
{
//...
if (includeCustomerComplaints)
{
var allComplaintsData = GetAllComplaintsDataViaWebService(
customerId,
includeOnlyOpenCustomerComplaints);
www.dotnetcurry.com/magazine
83
var complaintsSection =
includeOnlyASummaryOfCustomerComplaints
? GenerateSummaryOfComplaintsSection(allComplaintsData)
: GenerateAllComplaintsSection(allComplaintsData);
sections.Add(complaintsSection);
}
}
//...
Such implementation pushes a developer to have a single includeOnlyOpenCustomerComplaints
parameter in the GenerateReport method.
Take a look at MainWindow.xaml.cs in Tool5 to see how the input is collected from the UI and how the
GenerateReport method is called.
So, the signature of GenerateReport did not change between Tool4 and Tool5. What changed are the valid
(or meaningful) combinations of parameter values. For example, the following combination:
GenerateReport(
customerId: 1,
outputFilename: "c:\\outputfile.pdf",
includeCustomerComplaints: true,
includeOnlyOpenCustomerComplaints: true,
includeOnlyASummaryOfCustomerComplaints: true);
..is not meaningful in Tool4 but is meaningful in Tool5. That is, in Tool4, the value of
includeOnlyOpenCustomerComplaints is ignored when
includeOnlyASummaryOfCustomerComplaints is true; while in Tool5, the value of the same parameter
in the same case is not ignored.
Another confusing thing about the signature of GenerateReport in Tool4 and Tool5 is the relationship
between includeCustomerComplaints and includeOnlyASummaryOfCustomerComplaints. Is
includeOnlyASummaryOfCustomerComplaints considered only if includeCustomerComplaints is
true? Or should only one of these be true? The signature does not answer these questions.
The GenerateReport method can choose to throw an exception if it gets an invalid combination, or it may
choose to assume certain things in different cases.
Let’s go back to the question I asked before: why do we see bad code too much? Why is this unclarity seen
more in code, than in UI?
I have the following reasons in mind:
1. UI is what the end users see from the application, and thus fixing any unclarity in this area is given a
higher priority.
2. Many programming languages do not give us a convenient way to model parameters correctly. That is, it
is more convenient to model parameters incorrectly.
The first point is clear, and I will not discuss it any further.
The second point needs more explanation, I think.
84
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
A function parameter list is a product
type
In a previous article, Designing Data Objects in C# and F#, I talked about Algebraic Data Types: sum types
and product types.
In a nutshell, a sum type is a data structure that can be any one of a fixed set of types.
For example, we can define a Shape sum type that has the following three sub-types:
1. Square (int sideLength)
2. Rectangle (int width, int height)
3. Circle (int diameter)
That is, an object of type Shape can either be Square, Rectangle, or Circle.
A product type is a type that defines a set of properties, that an instance of the type needs to provide
values for. For example, the following is a product type:
public sealed class Person
{
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string Name { get; }
public int Age { get; }
}
This Person class defines two properties; Name and Age. An instance of the Person class should specify a
value for Name and a value for Age. Any value of type string should be valid for the Name property and
any value of type int should be valid for the Age property.
The constructor of the Person class should not do any validation. That is, I would say that the following is
not a product type:
public sealed class Person
{
public Person(string name, int age)
{
if (Age > 130 || Age < 0)
throw new Exception(nameof(Age) + " should be between 0 and 130");
}
Name = name;
Age = age;
public string Name { get; }
}
public int Age { get; }
www.dotnetcurry.com/magazine
85
It's not a product type of string and int. Any string and int values cannot be used to construct a
Person.
However, the following Person class is a product type:
public sealed class Person
{
public Person(string name, Age age)
{
Name = name;
Age = age;
}
public string Name { get; }
public Age Age { get; }
}
public sealed class Age
{
public Age(int value)
{
if (value > 130 || value < 0)
throw new Exception(nameof(Age) + " should be between 0 and 130");
Value = value;
}
public int Value { get; }
}
Person is a product type of string and Age. Any string and any Age can be used to construct a Person
class. Age is not a product type, it is a special type that models an age of a person.
We should think about a parameter list as a product type, any combination of parameter values (i.e.,
arguments) should be valid. A function should not throw an exception because a certain combination
of values passed to it is invalid. Instead, function parameters should be designed in a way that any
combination is valid.
I am not saying that functions should not throw exceptions at all, that is a topic for a different article.
How to make all combinations valid?
The flattened sum type anti-pattern
Let’s refactor the GenerateReport method to take a parameter of type ComplaintsReportingSettings
instead of the three boolean parameters. This class would simply contain the three boolean values as
properties.
void GenerateReport(
int customerId,
string outputFilename,
ComplaintsReportingSettings complaintsReportingSettings)
public sealed class ComplaintsReportingSettings {
public ComplaintsReportingSettings(
bool includeCustomerComplaints,
bool includeOnlyOpenCustomerComplaints,
bool includeOnlyASummaryOfCustomerComplaints)
86
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
{
}
}
IncludeCustomerComplaints = includeCustomerComplaints;
IncludeOnlyOpenCustomerComplaints = includeOnlyOpenCustomerComplaints;
IncludeOnlyASummaryOfCustomerComplaints =
includeOnlyASummaryOfCustomerComplaints;
public bool IncludeCustomerComplaints { get; }
public bool IncludeOnlyOpenCustomerComplaints { get; }
public bool IncludeOnlyASummaryOfCustomerComplaints { get; }
What is wrong with the ComplaintsReportingSettings type?
The problem is that not all combinations of the properties (or the constructor’s parameters) are valid. Let’s
make this more explicit:
public sealed class ComplaintsReportingSettings
{
public ComplaintsReportingSettings(
bool includeCustomerComplaints,
bool includeOnlyOpenCustomerComplaints,
bool includeOnlyASummaryOfCustomerComplaints)
{
if (includeOnlyOpenCustomerComplaints && !includeCustomerComplaints)
throw new Exception(nameof(includeOnlyOpenCustomerComplaints) + " is
relevant only if " + nameof(includeCustomerComplaints) + " is true");
if (includeOnlyASummaryOfCustomerComplaints && !includeCustomerComplaints)
throw new Exception(nameof(includeOnlyASummaryOfCustomerComplaints) + " is
relevant only if " + nameof(includeCustomerComplaints) + " is true");
IncludeCustomerComplaints = includeCustomerComplaints;
IncludeOnlyOpenCustomerComplaints = includeOnlyOpenCustomerComplaints;
IncludeOnlyASummaryOfCustomerComplaints =
includeOnlyASummaryOfCustomerComplaints;
}
}
public bool IncludeCustomerComplaints { get; }
public bool IncludeOnlyOpenCustomerComplaints { get; }
public bool IncludeOnlyASummaryOfCustomerComplaints { get; }
The added statements make sure that combinations that are not valid or that are not meaningful, will cause
an exception to be thrown.
If we are designing this class for Tool4, then we would add another validation statement in the constructor:
if (includeOnlyOpenCustomerComplaints && includeOnlyASummaryOfCustomerComplaints)
throw new Exception(nameof(includeOnlyOpenCustomerComplaints) + " should not be
specified if " + nameof(includeOnlyASummaryOfCustomerComplaints) + " is true");
The ComplaintsReportingSettings class is an example of what I call the flattened sum type antipattern. That is, it is something that should be designed as a sum type but is instead flattened into what
looks like a product type (but is actually not a product type).
Once you see a type that looks like a product type but of which there is a combination (or combinations) of
www.dotnetcurry.com/magazine
87
its properties that is invalid or not meaningful, then you have identified an instance of this anti-pattern.
Take a look at the Tool6 project. Here is the signature of the GenerateReport method:
void GenerateReport(
int customerId,
string outputFilename,
ComplaintsReportingSettings complaintsReportingSettings)
But the ComplaintsReportingSettings type looks like this:
public abstract class ComplaintsReportingSettings {
private ComplaintsReportingSettings()
{
}
public sealed class DoNotGenerate : ComplaintsReportingSettings
{
}
public sealed class Generate : ComplaintsReportingSettings
{
public Generate(bool includeOnlyOpenCustomerComplaints)
{
IncludeOnlyOpenCustomerComplaints = includeOnlyOpenCustomerComplaints;
}
}
public bool IncludeOnlyOpenCustomerComplaints { get; }
public sealed class GenerateOnlySummary : ComplaintsReportingSettings
{
public GenerateOnlySummary(bool includeOnlyOpenCustomerComplaintsForSummary)
{
IncludeOnlyOpenCustomerComplaintsForSummary =
includeOnlyOpenCustomerComplaintsForSummary;
}
}
}
public bool IncludeOnlyOpenCustomerComplaintsForSummary { get; }
This is a sum type with three cases. I talked about modeling sum types in C# in the Designing Data Objects
in C# and F# article. In a nutshell, a sum type is designed as an abstract class with a private constructor.
Each subtype is designed as a sealed class nested inside the sum type class.
Please compare this to how the UI looks like. Here it is again (the same as Tool5):
88
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Can you see the similarities between the sum type version of ComplaintsReportingSettings and the
UI? In some sense, radio buttons together with the ability to enable/disable certain controls based on user
selection of radio buttons, help us model sum types in the UI.
It is easy to understand now why developers use the flattened sum type anti-pattern: it is more convenient!
It is much easier to add a boolean parameter to a method than to create a sum type in C#.
C# might get sum types in the future. See this Github issue here: https://github.com/dotnet/csharplang/
issues/113
Another reason why developers use this anti-pattern is that they are not aware of it. I hope that by writing
this article, developers are more aware of it.
This anti-pattern can be more complicated. For example, it might be the case that two sum types are
flattened into a single product type (in a parameter list). Consider this example:
void GenerateReport(
int customerId,
string outputFilename,
bool includeCustomerComplaints,
bool includeOnlyOpenCustomerComplaints,
bool includeOnlyASummaryOfCustomerComplaints,
bool includeCustomerReferrals,
bool includeReferralsToCustomersWhoDidNoOrders,
Maybe<DateRange> dateRangeToConsider)
This updated GenerateReport method allows users to specify whether they want to include details about
customer referrals in the report. Also, it allows the users to filter out referrals that yielded no orders. The
dateRangeToConsider is a bit special. This parameter is added to enable the caller to filter some data out
of the report based on a date range. The parameter is of type Maybe<DateRange>. Maybe is used to model
an optional value. For more information about Maybe, see the Maybe Monad article here
www.dotnetcurry.com/patterns-practices/1510/maybe-monad-csharp.
Although, it is not clear from the signature, this parameter affects not just referral data, it also effects
customer complaints data when the full list of complaints is to be included. That is, it affects these data:
•
•
Customer referral data
Complaints data when a full list is requested
And it does not affect the following:
•
Complaints data when only a summary is requested.
The signature of the method in no way explains these details.
The last six parameters of the GenerateReport method should be converted to two sum types,
ComplaintsReportingSettings and ReferralsReportingSettings:
public abstract class ComplaintsReportingSettings {
private ComplaintsReportingSettings()
{
www.dotnetcurry.com/magazine
89
}
public sealed class DoNotGenerate : ComplaintsReportingSettings
{
}
public sealed class Generate : ComplaintsReportingSettings
{
public Generate(bool includeOnlyOpenCustomerComplaints, DateRange
dateRangeToConsider)
{
IncludeOnlyOpenCustomerComplaints = includeOnlyOpenCustomerComplaints;
DateRangeToConsider = dateRangeToConsider;
}
public bool IncludeOnlyOpenCustomerComplaints { get; }
}
public DateRange DateRangeToConsider { get; }
public sealed class GenerateOnlySummary : ComplaintsReportingSettings
{
public GenerateOnlySummary(bool includeOnlyOpenCustomerComplaintsForSummary)
{
IncludeOnlyOpenCustomerComplaintsForSummary =
includeOnlyOpenCustomerComplaintsForSummary;
}
}
}
public bool IncludeOnlyOpenCustomerComplaintsForSummary { get; }
public abstract class ReferralsReportingSettings
{
private ReferralsReportingSettings()
{
}
public sealed class DoNotGenerate : ReferralsReportingSettings
{
}
public sealed class Generate : ReferralsReportingSettings
{
public Generate(bool includeReferralsToCustomersWhoDidNoOrders,
Maybe<DateRange> dateRangeToConsider)
{
IncludeReferralsToCustomersWhoDidNoOrders =
includeReferralsToCustomersWhoDidNoOrders;
DateRangeToConsider = dateRangeToConsider;
}
public bool IncludeReferralsToCustomersWhoDidNoOrders { get; }
}
90
}
public Maybe<DateRange> DateRangeToConsider { get; }
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Note the following:
1. Only ComplaintsReportingSettings.Generate and ReferralsReportingSettings.
Generate have a DateRangeToConsider property. That is, only in these cases is the original
dateRangeToConsider parameter relevant.
2. The DateRangeToConsider property in ComplaintsReportingSettings.Generate is required
(does not use Maybe); while in ReferralsReportingSettings.Generate it is optional. The original
parameter list in GenerateReport did not explain such details, it couldn’t have!
Here is how an updated signature would look like:
void GenerateReport(
int customerId,
string outputFilename,
ComplaintsReportingSettings complaintsReportingSettings,
ReferralsReportingSettings referralsReportingSettings)
Note that a function parameter list should always be a product type, but it should be a valid
one. Here, the parameter list of the GenerateReport method is a product type of int,
string, ComplaintsReportingSettings, and ReferralsReportingSettings. However,
ComplaintsReportingSettings and ReferralsReportingSettings are sum types.
Conclusion:
In this tutorial, I talked about function parameters. I have demonstrated an anti-pattern which I call the
flattened sum type anti-pattern, via an example. In this anti-pattern, a type that should be designed as a
sum type (or more than one sum type) is designed as something that looks like a product type.
Because function parameters lists look naturally like product types and because sum types don’t have a lot
of support in the C# language and the tooling around it, it is more convenient for developers to just add
new parameters to the list, than to correctly design some of the parameters as sum types.
Download the entire source code from GitHub at
bit.ly/dncm44-sumtype
Yacoub Massad
Author
Yacoub Massad is a software developer who works mainly with Microsoft technologies. Currently, he works
at Zeva International where he uses C#, .NET, and other technologies to create eDiscovery solutions. He
is interested in learning and writing about software design principles that aim at creating maintainable
software. You can view his blog posts at criticalsoftwareblog.com.
Thanks to Damir Arh for reviewing this article.
www.dotnetcurry.com/magazine
91
.NET
Damir Arh
DEVELOPING
MOBILE APPLICATIONS
IN .NET
IN THIS TUTORIAL, I WILL DESCRIBE THE TOOLS AND
FRAMEWORKS AVAILABLE TO .NET DEVELOPERS FOR ALL
ASPECTS OF MOBILE APPLICATION DEVELOPMENT I.E.
FRONT-END, BACK-END AND OPERATIONS.
I
n many ways Mobile development is like
Desktop development.
Both Mobile and Desktop applications run
natively on the device and their user interface
depends on the operating system they are running
on.
However, there’s currently no operating system
that runs both on desktop and mobile!
Therefore, the tools and frameworks used
for application development are different.
Additionally, because mobile applications don’t
run natively on the same device as they are being
developed, the development experience isn’t as
92
smooth as with desktop applications.
Front-end
Development
When talking about mobile development, one
usually thinks of an application running on a mobile
device. This is usually referred to as front-end
development.
In the .NET ecosystem, Xamarin is the obvious choice
when deciding on a framework to develop a mobile
application in. Despite that, Unity or MonoGame
might be a better fit in some scenarios.
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Xamarin
Xamarin is the .NET implementation for mobile devices. It was developed by a company with the same
name which was acquired by Microsoft in 2016. Although Xamarin was originally a paid product, it is
available for free to all developers ever since the acquisition.
There are two approaches to developing user interfaces with Xamarin:
Approach 1# In the traditional Xamarin approach, you create a new project for a single platform (Android or
iOS).
In this case, native technologies are used for creating the views (layouts in Android XML files for Android,
and storyboards for iOS). For example, the following Android layout file could be used in a traditional
Xamarin project or a native Android project without any changes:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/activity_main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello World!" />
</RelativeLayout>
The code for the views is still written in C# instead of Java, Kotlin, Objective C or Swift and uses .NET base
class libraries. Thanks to the .NET binding libraries provided by the Xamarin platform, Native platformspecific APIs can also be invoked directly from C#.
Approach 2# When creating a new project for multiple platforms, Xamarin.Forms UI framework is used
instead.
It’s somewhat similar to WPF and UWP in that it is XAML-based and works well with the MVVM pattern.
(You can read more about WPF and UWP, and XAML in my tutorial from the previous issue of DotNetCurry
Magazine – Developing desktop applications in .NET.) However, the control set is completely different
because under the hood it’s just an abstraction layer on top of the traditional Xamarin approach. This means
that individual controls are rendered differently on different platforms: each Xamarin.Forms control maps to
a specific native control on each platform.
Here’s an example of a simple Xamarin.Forms-based UI layout:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns=http://xamarin.com/schemas/2014/forms
xmlns:x=http://schemas.microsoft.com/winfx/2009/xaml
xmlns:d=http://xamarin.com/schemas/2014/forms/design
xmlns:mc=http://schemas.openxmlformats.org/markup-compatibility/2006
mc:Ignorable="d"
www.dotnetcurry.com/magazine
93
x:Class="XamarinApp1.Views.MenuPage"
Title="Menu">
<StackLayout VerticalOptions="FillAndExpand">
<ListView x:Name="ListViewMenu"
HasUnevenRows="True">
<d:ListView.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Item 1</x:String>
<x:String>Item 2</x:String>
</x:Array>
</d:ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10">
<Label Text="{Binding Title}"
d:Text="{Binding .}"
FontSize="20"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
Although Xamarin.Forms initially imposed many limitations on the interfaces which could be developed
with them, they have improved a lot since then. They are already at version 4 and are probably used for
development of most new Xamarin applications today. In addition to Android and iOS, they also provide full
support for UWP (i.e. a single Xamarin application can run on Android, iOS and Windows). Along with the
three fully supported platforms, three more are currently in preview: WPF, macOS and GTK# (a wrapper on
top of the GTK cross-platform UI toolkit). Tizen .NET adds support for Tizen applications as well (Samsung’s
operating system for various smart devices).
In cases when the Xamarin.Forms control set doesn’t suffice; the so-called native views can be used to
complement them. This feature gives access to native UI controls of each individual platform. They can be
declared alongside Xamarin.Forms controls in the same XAML file.
Of course, each native UI control will only be used in building the UI for the corresponding platform.
Therefore, a different native control will usually be used in the same place for each platform supported by
the application, as in the following example:
<StackLayout Margin="20">
<ios:UILabel Text="Hello World"
TextColor="{x:Static ios:UIColor.Red}"
View.HorizontalOptions="Start" />
<androidWidget:TextView Text="Hello World"
x:Arguments="{x:Static androidLocal:MainActivity.Instance}" />
<win:TextBlock Text="Hello World" />
</StackLayout>
On mobile devices, UI controls are not the only platform specific part of the application. Some APIs are
also platform specific, mostly those for accessing different device sensors (such as geolocation, compass,
accelerometer etc.) and platform specific functionalities (e.g. clipboard, application launcher, dialer etc.). The
Xamarin.Essentials library can make their use simpler by providing its own unified API for them across all
94
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
supported platforms. Here’s an example of code for retrieving current geolocation:
var request = new GeolocationRequest(GeolocationAccuracy.Medium);
var location = await Geolocation.GetLocationAsync(request);
All of this makes Xamarin a feature-complete framework for developing mobile applications for Android
and iOS. In this field, it is the only choice for .NET developers who want to leverage their existing .NET and
C# knowledge for mobile application development.
Xamarin.Forms support for other platforms might make the framework an appropriate choice when trying
to target other devices with the same application (especially once all supported platforms come out of
preview). However, there aren’t many applications which would make sense on such a wide variety of
devices with the same codebase, and this could be one of the limiting factors.
To read more about Xamarin, check out some tutorials at www.dotnetcurry.com/tutorials/xamarin as well as
at https://docs.microsoft.com/en-us/xamarin/.
Unity and MonoGame
Unity and MonoGame are also based on .NET and can be used to develop applications which run on
Android and iOS (but also on Windows, macOS and Linux, as well as most gaming consoles on the market
today). In contrast to Xamarin which is meant for general-purpose application development, Unity and
MonoGame primarily target (cross-platform) game development.
They take a different approach to enabling that:
- Unity is a 3D game engine with an easy-to-learn graphical editor. When developing a game, the editor is
used for designing scenes which usually represent game levels, although they are also used for creating
game menus, animations and other non-gameplay parts of a game.
Figure 1: Unity Editor main window
www.dotnetcurry.com/magazine
95
C#/.NET is only used for writing scripts which implement the logic to control the objects created through
the editor, and bring the game to life. Here’s a typical example of a script in Unity:
public class PlayerDeath : Simulation.Event<PlayerDeath>
{
PlatformerModel model = Simulation.GetModel<PlatformerModel>();
public override void Execute()
{
var player = model.player;
if (player.health.IsAlive)
{
player.health.Die();
model.virtualCamera.m_Follow = null;
model.virtualCamera.m_LookAt = null;
player.controlEnabled = false;
}
}
}
if (player.audioSource && player.ouchAudio)
player.audioSource.PlayOneShot(player.ouchAudio);
player.animator.SetTrigger("hurt");
player.animator.SetBool("dead", true);
Simulation.Schedule<PlayerSpawn>(2);
- MonoGame is a much more low-level tool than Unity. It implements the API introduced by Microsoft XNA
Framework – a .NET wrapper on top of DirectX which was discontinued in 2014.
It’s not a full-blown game engine, but it does provide a content pipeline for managing and processing
the assets (sounds, images, models, etc.) used in the game. Still, developing a game in MonoGame mostly
consists of writing .NET code responsible for managing the game state as well as doing the rendering. As
you can see in the following example, MonoGame code is typically at a much lower level of abstraction
than Unity code:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(ballTexture, new Vector2(0, 0), Color.White);
spriteBatch.End();
}
base.Draw(gameTime);
Apart from games, Unity and MonoGame can also be used for developing other types of applications,
especially when 3D visualization plays an important role in them. Good candidates are applications in the
field of augmented reality (AR), industrial design and education. Unity is recently focusing on providing the
tools to make development of such applications easier.
When deciding to develop an application this way instead of using general-purpose frameworks like
Xamarin, it’s important to keep in mind the potential downsides of running a 3D engine inside your
application. These considerations are even more important on a mobile device with limited processing
power, connectivity, and battery life: higher battery usage, longer initial load time and larger application
size. It is important that the benefits in a specific scenario outweigh them before deciding for such an
approach.
96
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
Building and Running the Mobile Applications
Because mobile applications target a different device, they can’t be simply run on the computer where they
are developed. They need to be deployed to an emulator or a physical device and run from over there.
For Android applications, this works on all desktop operating systems. Both Visual Studio and Visual Studio
for Mac will install all the necessary dependencies to make it work, if you choose so. The built-in user
interface will allow you to manage the installed versions of Android SDK and configured virtual devices
for the Android emulator. When starting an Android application from Visual Studio, you can select which
emulated or physical device to use.
Figure 2: Android Device Manager in Visual Studio 2019
Because of Apple’s licensing restrictions, this is not possible for iOS applications. To build and deploy an iOS
application, a computer running macOS is required. To make development on a Windows computer more
convenient, Visual Studio can pair with a macOS computer which will be used to build the iOS application
and to deploy it to a physical iOS device (which must also be connected to the macOS computer).
Figure 3: Pairing to Mac in Visual Studio 2019
The iOS Simulator can also run only on a macOS computer. To avoid any interaction with macOS desktop
when developing on Windows, Xamarin comes with Remoted iOS Simulator for Windows. When Visual
Studio is paired with a macOS computer, it can run the iOS application in the iOS Simulator on Mac and
www.dotnetcurry.com/magazine
97
show its screen in the Remoted iOS Simulator window. This window can also be used to interact with the
iOS application as if the simulator was running locally on the Windows computer.
Back-end Mobile Development
In most cases, the application running on the mobile device is only a part of the complete solution. The
data shown and manipulated in the application must be retrieved from and stored to somewhere. Unless
the application is a client for an existing public service, it will require a dedicated back-end service for this
functionality.
ASP.NET Core and ASP.NET Web API
The back-end service will typically expose a REST endpoint for the mobile application to communicate
with. As explained in more detail in my previous DotNetCurry article Developing Web Applications in .NET
(Different Approaches and Current State), these can be developed using ASP.NET Core or ASP.NET Web API.
From the viewpoint of the back-end service, a mobile application is no different from a web SPA (singlepage application).
This approach makes most sense when the mobile application is developed as a companion or an
alternative to a web application with a similar functionality because the same back-end service can be
used for both. It’s also appropriate when the back-end is not just a simple data-access service but needs to
implement more complex business logic. The higher overhead of a dedicated web application gives full
freedom to what can be implemented inside it.
Mobile Apps in Azure App Service
A standalone web application as the back-end might be an overkill if the mobile application only needs
some standard functionality such as authentication, cloud storage for user settings and other data, usage
analytics and similar. The Mobile Apps flavor of Azure App Service might be a good alternative in such
cases.
It provides the following key functionalities implemented in dedicated server and client SDKs:
•
Data access to any data source with an Entity Framework data provider (Azure or on-premise SQL
Server, Azure Table Storage, MongoDB, Cosmos DB, etc.) with support for offline data synchronization.
•
User authentication and authorization using social identity providers (Microsoft, Facebook, Google or
Twitter) for consumer applications or Active Directory for enterprise applications.
•
Push notifications using Azure Notification Hubs.
98
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
The .NET Server SDK consists of a collection of Microsoft.Azure.Mobile.Server.* NuGet packages which
must be installed into an empty ASP.NET Web Application project for .NET Framework. The Microsoft.Azure.
Mobile.Server.Quickstart NuGet package can be used for a basic setup with all supported functionalities, but
there are also separate NuGet packages available for each functionality. The SDK needs to be initialized in
an OWIN startup class:
[assembly: OwinStartup(typeof(MobileAppServer.Startup))]
namespace MobileAppServer
{
public class Startup
{
// in OWIN startup class
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
}
}
}
app.UseWebApi(config);
Individual functionalities can now be added with minimum code:
•
For data access, an Entity Framework DbContext class must be created first with DbSet<T> instances
for every table to be exposed. Then, for each table, a TableController<T> can be created using the
scaffolding wizard for creating a new Azure Mobile Apps Table Controller:
Figure 4: Scaffolding wizard for a new Azure Mobile Apps Table Controller
•
For authorization, the controller classes and action methods only need to be decorated using the
standard Authorize attribute.
•
For push notifications, the code sending them needs to instantiate the NotificationHubClient class and
use its methods to send notifications.
•
Additional custom API controllers can be exposed to the mobile application by decorating them with
the MobileAppControllerAttribute class.
All of this is explained in more details in the .NET SDK Server documentation.
www.dotnetcurry.com/magazine
99
There’s a corresponding Client SDK available for several different technology stacks. Xamarin applications
can use the .NET Client SDK by installing the Microsoft.Azure.Mobile.Client NuGet package. All calls to the
Mobile Apps backend are available as methods of the MobileServiceClient class:
•
To access data, the GetTable<T> and GetSyncTable<T> methods are available. The latter implements
offline synchronization but requires some additional setup before it can be used:
°° The Microsoft.Azure.Mobile.Client.SQLiteStore NuGet package must be installed.
°° An instance of the MobileServiceSQLiteStore class must be provided to the
MobileServiceClient instance:
var localStore = new MobileServiceSQLiteStore(localDbPath);
localStore.DefineTable<TodoItem>();
mobileServiceClient.SyncContext.InitializeAsync(localStore);
•
•
Authentication can be done using the LoginAsync method.
To register an application for push notifications, additional platform specific setup is required using
the dedicated Google and Apple services. Only the corresponding device token is sent to the backend
using the RegisterAsync method on the platform specific object returned by the call to the
MobileServiceClient’s GetPush method. Handling of received push notifications is done using the
standard API’s on each platform.
Microsoft has stopped further development of Azure Mobile Apps SDKs in favor of its Visual Studio App
Center solution for mobile application development. However, for most of the functionalities described
above, Visual Studio App Center doesn’t yet provide a functionally equivalent alternative which still makes
Azure Mobile Apps the only choice if you need any of its unique features. This will very likely change as
Visual Studio App Center is further developed.
Visual Studio App Center
Visual Studio App Center is a web portal solution with a primary focus on DevOps for mobile applications
as provided by three core services:
•
Build can connect to a Git repository in Azure DevOps, GitHub, Bitbucket or GitLab and build an Android
or iOS mobile application developed in one of the supported technology stacks (Xamarin and Unity are
among them, of course).
•
Test can run automated UI tests for mobile applications directly on hardware devices, not on emulators.
A selection of several hundred devices is available to choose from. Multiple test frameworks are
supported as well: Xamarin.UITest and Appium on both platforms, Espresso on Android and XCUITest on
iOS. The service is the successor of Xamarin Test Cloud.
•
Distribute is responsible for application deployment. In addition to the official public Google Play and
App Store services, Microsoft’s Intune mobile device management solution is supported as well. The
latter can be used for deploying internal applications in corporate environments.
100
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
All of these can be controlled and tracked from the web portal.
The next important part of Visual Studio App Center are Diagnostics and Analytics services. To take
advantage of them, the App Center’s SDK must be added to the mobile application. For Xamarin
applications, the Microsoft.AppCenter.Analytics and Microsoft.AppCenter.Crashes NuGet packages need to
be installed.
To initialize basic functionality, a single call to AppCenter.Start during application startup is enough.
Additional methods are available for tracking custom events in applications and for reporting errors which
were handled in code and didn’t cause the application to crash.
The web portal provides the tools to monitor and analyze all the analytical and diagnostical data
submitted from the application. These can be used to track application stability, resolve reported crashes or
errors, and explore application usage patterns to improve user experience and retention.
The remaining services to some extent overlap with Azure Mobile Apps:
•
•
•
Data caters to application’s data access needs both in online and offline scenario. However, unlike data
access features in Azure Mobile Apps it doesn’t build on top of Entity Framework data providers to
provide this functionality. Instead, it relies on the features of Azure Cosmos DB database service which
is the only supported data source.
Auth provides user authentication by integrating Azure Active Directory B2C identity management
service. In comparison to user authentication in Azure Mobile Apps it supports more social identity
providers but can’t be used in conjunction with a corporate Active Directory.
Push provides direct integration with Android and iOS native notification services (Firebase Cloud
Messaging and Apple Push Notifications, respectively) and doesn’t use the Azure Notification Hubs
service to achieve that like Azure Mobile Apps does.
Like Diagnostics and Analytics services, these three services also have corresponding Client SDK packages
which make them easier to use. For Xamarin applications, these are the Microsoft.AppCenter.* NuGet
packages.
It will depend on your application requirements which data access, authentication and push notification
services will suit you better (the ones from Azure Mobile Apps or the ones from Visual Studio App Center).
If you can achieve your goals with either, then out of the two, Visual Studio App Center currently seems a
better choice for two reasons:
•
There are additional DevOps services in Visual Studio App Center that are not available in Azure Mobile
Apps. If you decide to take advantage of these, then using Visual Studio App Center also for services
that have alternatives in Azure Mobile Apps, will allow you to manage everything from a single location
– the Visual Studio App Center web portal.
•
Unlike Azure Mobile Apps, Visual Studio App Center is still being actively developed which makes it a
safer choice for the future. Currently, Azure Mobile Apps are still fully supported but it’s impossible to
say whether or when this will change.
One can also safely assume that Visual Studio App Center will only improve with time. Existing services will
get additional features and new services might be added.
www.dotnetcurry.com/magazine
101
Conclusion
For front-end application development, most .NET developers will choose Xamarin as the only generalpurpose application framework. Unity and MonoGame are a great alternative for game development and
can sometimes make sense for development of other applications with a lot of 3D visualization, e.g. in the
field of augmented reality.
For back-end development, the best choice is not as obvious. Visual Studio App Center is the most feature
complete offering and Microsoft continues to heavily invest in it. This makes it a good choice for most
new applications. Azure Mobile Apps offers only a subset of its functionalities but can sometimes still be a
better fit because it takes a different approach to them. Unfortunately, it's not actively developed anymore
which makes it a riskier choice.
ASP.NET Core and ASP.NET Web API can be a viable alternative to the cloud services if you already have
your own custom back-end or just want to have more control over it.
Damir Arh
Author
Damir Arh has many years of experience with Microsoft development tools; both in
complex enterprise software projects and modern cross-platform mobile applications.
In his drive towards better development processes, he is a proponent of test driven
development, continuous integration and continuous deployment. He shares his
knowledge by speaking at local user groups and conferences, blogging, and answering
questions on Stack Overflow. He is an awarded Microsoft MVP for .NET since 2012.
Thanks to Gerald Verslius for reviewing this article.
102
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
www.dotnetcurry.com/magazine
103
AZURE
Gouri Sohoni
AUTOMATION
IN
MICROSOFT AZURE
The Azure Automation feature automates frequently required, time consuming
tasks.
Once automated, it reduces the workload, decreases bugs and increases efficiency.
Azure automation is also available for Hybrid mode to automate tasks in other
clouds or on-premises environments.
In this tutorial, I will discuss various aspects about Azure Automation that includes
•
•
•
•
•
•
104
Runbooks
Schedules
Jobs and webhooks for running runbooks
Desired State Configuration (DSC) to create configurations and applying it to
nodes
Update Management for virtual machines
Source Control Integration for keeping the configurations in Source Control.
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
AZURE AUTOMATION - OVERVIEW
Azure Automation is a service in Microsoft Azure that lets you automate management related tasks.
It also lets you create containers for Runbooks (a compilation of routine procedures and operations that the
system administrator or operator carries out).
These runbooks have scripts for executing certain tasks and can be either automatically executed using
schedules or jobs, or can be triggered using webhooks. We have a way to specify all your virtual machines
in a required state (DSC). We also have the option of making the VMs comply to a desired state by applying
(and correcting if required) the configuration to maintain the state.
AUTOMATION ACCOUNT
Creating Automation Account is the first step to start using all the features of Azure Automation. Sign in
to https://portal.azure.com to avail all these services. If you do not have an Azure Account, use this link to
create a free account.
Create Automation Account
Click on Create a resource after you sign in to the Azure portal
Select IT & Management Tools – Automation. Provide name for the account, resource group (container for a
number of resources) and select location. Select Create Azure Run As account and click on Create
For selection of a location, you may take a look at these Global Infra Services.
Select the account once it is created.
www.dotnetcurry.com/magazine
105
RUNBOOK
•
Runbook can have scripts which needs to be executed frequently. Once created, provide the schedule
for the execution either by creating a schedule or by creating a job which in turn will trigger the
execution.
•
Now that the automation account is ready, add different tasks to it which are required frequently. There
are a lot of tutorials available for runbooks. You can explore all these tutorials by selecting Runbook
from Process Automation tab. These tutorials are automatically created for you, in the account.
Create a Runbook
•
Click on Create a runbook from Process Automation – Runbooks, provide name and select the type of
runbook as PowerShell. Provide an appropriate description and click on Create
•
An IDE appears to write the script. You can write as complex PowerShell commands as required. I have
just created a demo runbook.
106
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
•
Save the runbook and click on the Test pane to view how it works. Start the runbook and you will get a
message saying that it is Queued. If you have added any parameters, you need to provide their values.
After successful execution, the test pane shows us the result as follows:
•
When we want the runbook to be available via a schedule or url which can be given in a webjob, we
have to publish the runbook. Click on Publish, you’ll see a message saying this will overwrite if it already
exists.
To monitor a particular folder and send a log message when any changes happen to the folder, create a
recurring schedule which will be automatically triggered every 15 minutes by using a schedule.
Sometimes you may want to provide automated messages or information to other apps, this can be
achieved by using webhooks. This feature is available by using the URL provided to trigger the runbook.
ADD A SCHEDULE AND VIEW JOB
•
Let us create a schedule for execution of the published runbook. Select Schedules tab from Resources,
Create on New Schedule, enter the name, specify the time to start along with your time zone, provide
whether you want the runbook to be triggered recurring or just once and finally click on Create. As a
best practice, keep a difference of at least a 5 minutes time lag at start time for schedule, and the time
at which we are creating it.
•
Link the schedule to the runbook
•
We can find the schedule under Schedules tab. After the specified time, we can see the job executed
which in turn has triggered runbook execution. Create a Webhook to trigger the runbook.
www.dotnetcurry.com/magazine
107
TRIGGER RUNBOOK USING AN APPLICATION
As part of a requirement, you may need to trigger a runbook with the help of an application. In order to
achieve this, we can create a webhook for it. A Webhook will start any runbook using a HTTP request. This
can in turn be used by any other application (custom application) or services like Azure DevOps, GitHub.
•
Select Properties from Runbook settings and click on Add Webhook
•
Provide a name, select webhook expiration period and do not forget to copy the URL. Select Enabled,
click Ok and finally click on Create
•
The newly created Webhook can be found under the tab of Webhook from Resources
•
Now we need a way to trigger the runbook. You can use any of the above-mentioned ways. I am going
to use Postman to trigger (we cannot use browser as this is a HTTP POST)
•
When I send a POST request via postman, I can see another job triggered and thus it ensures that the
URL works. You can view the job and look at the Input, Output provided via runbook.
With the Overview tab, we can have a look at the overall Job Statistics for the account.
STATE CONFIGURATION - DESIRED STATE CONFIGURATION (DSC)
Azure Automation provides us with feature of DSC (similar to Windows Feature DSC Service) which helps in
keeping all the nodes (Virtual machines) in a particular state.
Say with the Continuous Deployment (CD) concept, if we need all the machines in our environment to have
IIS or Tomcat installed on them before the deployment succeeds, we can use DSC.
This feature can be used for receiving configurations, checking for desired state (which as per our
requirement can be to check IIS server or Apache Tomcat server) and responding back with the compliance
for the same.
In order to apply it to nodes, we first have to create configuration, compile it and later apply it to one or
multiple nodes in our subscription. We can provide the options for automatically applying the state and the
frequency with which the compliance can be checked. We can also specify if the node is not complying and
if it needs to be corrected.
The prerequisite for this is you need at least one virtual machine which can be configured for DSC. I have
created a machine with Windows Server 2012 Datacenter as OS. As I will be applying the IIS configuration
using a script, I have not configured IIS server to ensure that it is added at the time of compliance check.
Configuration: DSC configuration are PowerShell scripts. There is a keyword Configuration for specifying
the name of the configuration, followed by Node blocks. There can be more than one node separated by a
comma. After writing the script, we have to compile it which in turn creates a MOF document.
Create a PowerShell script with the following code and save with an appropriate name.
configuration LabConfig
{
108
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
}
Node WebServer
{
WindowsFeature IIS
{
Ensure = 'Present'
Name = 'Web-Server'
IncludeAllSubFeature = $true
}
}
•
•
Select Configurations from Configuration Management – State configuration (DSC) tab
Click on Add and select the .ps1 file we created earlier and click on Ok
•
You will see that the new configuration has been added, select it and click on Compile (read the
message carefully and click on Yes)
•
The configuration will appear under Compiled Configurations now
•
Click on Nodes and select Add. We will get a list of all the virtual machines we have created with an
ARM template.
•
Select the virtual machine which we want to configure for IIS (make sure that the machine is running).
•
Click on Connect and provide the required values. There are three values for Configuration mode namely
ApplyAndMonitor (this is the default behavior, if the node does not comply DSC reports error), ApplyOnly
(DSC just tries to apply configuration and does nothing further) and ApplyAndAutocorrect (if the target
node seizes to be compliant, DSC reports it and also reapplies the configuration). Use the one required
for your usage. The refresh frequency and configuration mode frequency values. Observe there is check
box for reboot node if required, finally click on the Ok button.
www.dotnetcurry.com/magazine
109
•
110
The node will be monitored after a successful configuration. If by any chance the IIS server is deleted
from the node, the next time the compliance is checked, it will be automatically corrected or we will
just get a message saying the node is not compliant any more.
DNC MAGAZINE ISSUE 44 - SEP - OCT 2019
If the machine is stopped in between, DSC will fail.
UPDATE MANAGEMENT
This feature from Azure Automation can be used to manage Operating System related updates for virtual
machines running Windows or Linux, or even for on premises machines.
This feature can be enabled using the automation account (it requires Log Analytics workspace to be linked
to your automation account). This can be applied to multiple subscriptions at a time.
Actual updates are taken care of by runbooks (though you cannot view them or do any configurations on
them).
SOURCE CONTROL INTEGRATION
We create a runbook by writing a PowerShell script.
On similar lines, we have added a PowerShell file for DSC to apply on a node. Using source control
integration, we can always use the latest changes made to any of the scripts. The source control can be
from GitHub or Azure DevOps (both TFVC as well as Git). You can create a folder in your repository which
will have all your runbooks.
Select Source Control tab from Account Settings, provide name, select the type and authenticate. We
can have a look at all the repositories when authentication succeeds. Select the repository, select the
branch, the folder and click on the Save button. We can also specify if we want to sync all our runbooks
automatically or not. If you have any runbooks as part of the source control, they will be updated and
published after successful syncing. We can add as many source controls as the number of gm runbooks we
have ready in different repositories.
CONCLUSION
In this article, we discussed how various tasks can be automatically managed by using Azure Automation.
Automating such tasks will help in better and efficient management.
Gouri Sohoni
Author
Gouri is a Trainer and Consultant specializing in Microsoft Azure DevOps. She has an experience of
over 20 years in training and consulting. She is a Microsoft MVP for Azure DevOps since 2011 and
is a Microsoft Certified Trainer (MCT). She is also certified as an Azure DevOps Engineer Expert and
Azure Developer Associate.
Gouri has conducted several corporate trainings on various Microsoft Technologies. She is a regular
author and has written articles on Azure DevOps (VSTS) and DevOps Server (VS-TFS) on
www.dotnetcurry.com. Gouri also speaks frequently for Azure VidyaPeeth, and gives talks in
conferences and events including Tech-Ed and Pune User Group (PUG).
Thanks to Subodh Sohoni for reviewing this article.
www.dotnetcurry.com/magazine
111
Thank You
for the 44th Edition
@dani_djg
@damirarh
@yacoubmassad
@subodhsohoni
@sommertim
@gouri_sohoni
@jfversluis
dnikolovv
@suprotimagarwal
@maheshdotnet
@saffronstroke
Write for us - mailto: suprotimagarwal@dotnetcurry.com
Descargar