SOFTWARE DEVELOPMENT NETWORK
MAGAZINE ISSN: 2211-6486
IN DIT NUMMER O.A.: Plug-in ontwikkeling op Dynamics CRM < ASP.NET Core 1.0
Docfx helps us make documentation easier for .NET < Goodbye HTML helpers and hello TagHelpers! <
Veilig beheren van Azure Resources vanuit C# <
Nummer 128 maart 2016 SDN Magazine verschijnt elk kwartaal en is een uitgave van Software Development Network
128
www.sdn.nl
Colofon Uitgave:
Software Development Network Vierentwintigste jaargang No. 128 • maart 2016
voorwoord
Bestuur van SDN:
Marcel Meijer, voorzitter Rob Suurland, penningmeester Remi Caron, secretaris
Redactie:
Beste SDN Magazine lezer,
Marcel Meijer (
[email protected])
Aan dit magazine werd meegewerkt door:
Roel Hans Bethlehem, Bob Swart, Maarten van Stam, Arjen Bos, Fanie Reynders, Rob Suurland, Remi Caron, Marcel Meijer en natuurlijk alle auteurs!
Listings:
Zie de website www.sdn.nl voor eventuele source files uit deze uitgave.
Contact:
Software Development Network Postbus 506, 7100 AM Winterswijk Tel. (085) 21 01 310 E-mail:
[email protected]
Vormgeving en opmaak:
Reclamebureau Bij Dageraad, Winterswijk www.bijdageraad.nl
©2015 Alle rechten voorbehouden. Niets uit deze uitgave mag worden overgenomen op welke wijze dan ook zonder voorafgaande schriftelijke toestemming van SDN. Tenzij anders vermeld zijn artikelen op persoonlijke titel geschreven en verwoorden zij dus niet noodzakelijkerwijs de mening van het bestuur en/of de redactie. Alle in dit magazine genoemde handelsmerken zijn het eigendom van hun respectievelijke eigenaren.
Adverteerders Microsoft Achmea
Nog een kleine maand en de Build conferentie zal weer plaats vinden. Ook deze editie was na enkele minuten uitverkocht. Wordt je als event organisator toch best jaloers van. De afgelopen jaren werd er altijd hardware weggegeven aan het publiek, maar dit jaar zouden ze dat niet doen. Mocht je niet in de gelegenheid zijn om naar San Francisco te gaan, gelukkig wordt de keynote met alle belangrijke mededelingen live en integraal uitgezonden. De SDN en de gezamenlijke Nederlandse usergroepen hebben weer een sponsor gevonden waar we gezamenlijk de keynote kunnen bekijken. Afsluitend kunnen we dan discussiëren over de nieuwtjes en het getoonde. In onze magazines en events komen we zeker terug op de inhoud. De Techdays zijn daarom verplaatst naar het najaar. Je zult ons daar zeker weer zien en horen. DEVintersection zal ook weer naar Nederland komen en wij willen rondom deze conferentie ook iets doen. Hou onze nieuwsbrieven in de gaten en reken in elk geval op een korting. Dit magazine bevat weer een paar gave artikelen van je collega’s. Hoe zij met de nieuwe technieken hebben gespeeld en hoe zij het hebben ingezet. Altijd leuk om de ervaringen te lezen voordat je er zelf mee gaat werken. Bas, Cary, Fanie, Kornelis, Henry, Danny, Roel Hans en Wouter hebben hun uiterste best gedaan om hun ervaringen mooi vorm te geven. Over Delphi, CRM, DocFX, DNN 8, Azure Resources, ASP.NET Core Project.json, Ubuntu en VS Team Services. Weet je dat Iedereen het in zich heeft om een artikel te schrijven? En weet je dat wij altijd kansen geven om dat ook te doen? Dus heb je zin om zelf een artikel te schrijven of een sessie te doen, meld je bij ons. We helpen je graag verder. Veel leesplezier, tot het SDN event en Build keynote viewing event. Groeten, Marcel Meijer eindredacteur Magazine en Voorzitter SDN •
2 28
Adverteren? Informatie over adverteren en de advertentietarieven kunt u vinden op www.sdn.nl onder de rubriek Magazine.
magazine voor software development 3
Agenda 2016 • maart Magazine 128 • 30 maart - 1 april Build Conference • 8 april SDN event • 16 april Global Azure Bootcamp • mei Magazine 129 • 10 juni SDN event • september Magazine 130 • 16 september SDN event • 26 - 30 september Ignite Conference • 4-5 oktober TechDays • november Magazine 131
Inhoud
03
Marcel Meijer
04 04 05
Voorwoord
Inhoudsopgave Agenda
Bas van de Sande
Plug-in ontwikkeling op Dynamics CRM
08
Bi-Directional Coupling: Here's One Way to Avoid It
11
Docfx helps us make documentation easier for .NET
13
Mijn eerste indruk van de DNN 8 release
15
Veilig beheren van Azure Resources vanuit C#
Cary Jensen
Fanie Reijnders
Kornelis Pieters Henry Been
18
ASP.NET Core 1.0: Goodbye HTML helpers and hello TagHelpers!
Danny van der Kraan
23
Migrate a .csproj from package.config to project.json
24
Setting Up Visual Studio Code On Ubuntu 14.04 Lts In Hyper-V
26
Code search in Visual Studio Team Services
Fanie Reijnders
Roel Hans Bethlehem Wouter de Kort
.NET
Bas van de Sande
Plug-in ontwikkeling op Dynamics CRM In mijn vorige artikelen over Dynamics CRM heb ik jullie in vogelvlucht meegenomen door Microsoft Dynamics CRM. Ik heb ik hoofdlijnen laten zien hoe Dynamics CRM in elkaar zit en heb ik laten zien op welke vlakken je binnen CRM kan customizen en ontwikkelen (zowel client side als server side). In dit artikel ga ik met een hoeksteen van Dynamics CRM aan de slag; de Plug-in. Wat is een plug-in? Een plug-in is een extra stukje maatwerk functionaliteit dat wordt geactiveerd op het moment dat wijzigingen binnen Dynamics CRM worden opgeslagen. Als ontwikkelaar kan je plug-ins gebruiken om Dynamics CRM om te vormen in het systeem waar de gebruikers mee kunnen werken. Wat er dan in feite gebeurt, is dat de gemaakte wijziging in een message wordt verpakt. Deze message wordt door een messaging pipeline wordt gehaald voordat de entiteit uiteindelijk wordt opgeslagen in de database van CRM. Het bericht moet succesvol door alle fasen binnen de pipeline heen. Op het moment dat in een van de fasen een fout optreedt, dan wordt de executie afgebroken en worden de wijzigingen niet opgeslagen!
• Stage 20 - Pre Operation In deze fase is de data nog niet opgeslagen in de database. Hier kan de data nog worden verrijkt of worden aangepast. In deze stap begint de transactie. • Stage 30 - Actual Operation In deze fase wordt de data opgeslagen in de database van CRM. Dit is een systeem fase, we hebben hierop geen enkele invloed. • Stage 40 - Post Operation In deze fase kunnen we nabewerkingen op de opgeslagen data doen. Met de voltooiing van deze fase wordt de transactie succesvol afgesloten en is de data definitief opgeslagen in CRM. Wat heb je nodig om plug-ins te ontwikkelen? Om plug-ins voor Dynamics CRM te kunnen ontwikkelen heb je naast Visual Studio (C# of VB.Net) de Dynamics CRM SDK nodig. Voor zowel CRM online als CRM on-premise gebruik je dezelfde SDK en tools. Waar je wel op moet letten is dat je de juiste versie van de SDK gebruikt (2016, 2015, 2013 of ouder). De SDK is te downloaden bij Microsoft. Verder heb je nog een Dynamics CRM omgeving nodig. Deze kan lokaal, op het netwerk of on-line zijn. Voor ontwikkeling is het het handigst als je een CRM on-premise versie gebruikt. De debugging mogelijkheden zijn hier uitgebreider dan op CRM online. Het is zeer zeker de moeite waard om de SDK nadat je deze hebt uitgepakt te bekijken. Je vindt hier onder andere:
Binnen de messaging pipeline zijn een aantal fasen waar het bericht doorheen gaat, te weten: • Stage 10 - Validation In deze fase kan je extra validatie loslaten op de data in de entiteit.
magazine voor software development 5
.NET
• CrmSDK20xx.chm Uitgebreide SDK documentatie. • Bin Hierin zijn de Dynamics CRM dll’s te vinden die je nodig hebt. Je kan deze ook via Nuget binnenhalen. • Tools Nuttige tools, waaronder de plug-in registration tool. De tool die je nodig hebt om je plug-in dll te registeren in CRM met de bijbehorende plugin stappen en images. My first plug-in We zijn nu klaar om onze eerste plug-in te ontwikkelen. We openen hiervoor Visual Studio en kiezen hier voor een class library project. Als dat is gedaan, dan kunnen we een stap verder gaan. We halen nu de entiteit uit de input parameters. Deze is voor create, update en delete berichten altijd te vinden onder ”Target”. Vervolgens kunnen we via de PluginExecutionContext bepalen met wat voor type bericht we hebben te maken en in welke fase dit bericht zich bevindt. In onderstaand voorbeeld luisteren we alleen naar een Create bericht in stage 20 (pre operation). Tenslotte overschrijven we de waarde van een van de velden in de entiteit. We hoeven de wijziging niet op te slaan. Deze wordt automatisch meegenomen in de execution pipeline.
Om tegen Dynamics CRM te kunnen programmeren moeten we een aantal references leggen, te weten Microsoft.Xrm.Sdk.dll en Microsoft.CRM.Sdk.Proxy.dll.
De plugin ziet er in basis een public class die de IPlugin interface implementeert. De IPlugin interface bevat alleen een Execute method, welke we moeten implementeren om iets met de plug-in te kunnen doen.
Op dit moment hebben we in principe onze eerste plugin geschreven. Wat nu nog rest is het signen (CRM accepteert alleen maar signed assemblies), compileren en installeren en registreren met de Plugin Registration Tool uit de SDK. Registreren plug-in en test run Na het opstarten van de Plugin Registration Tool kunnen we de plugin assembly toevoegen aan CRM. We kiezen hiervoor Register, Register New Assembly uit het menu. Een pop-up komt naar voren waarin we de plug-in dll kunnen selecteren.
Als eerste stap moet binnen de plug-in een PluginExecutionContext en een OrganizationService worden gemaakt. Uit de PluginExecutionContext haal je alle gegevens omtrent het bericht dat door de message pipeline heen gaat. Denk hierbij aan InputParameters, EntityImages, stage en MessageName. De OrganizationService heb je nodig om met Dynamics CRM te kunnen communiceren.
6
MAGAZINE
.NET
We laten de defaults staan en klikken op de Register Selected Plugins button. De plug-in wordt nu geuploaded naar Dyanmics CRM. We kunnen vervolgens gaan bepalen op welke stap de plug-in moet worden uitgevoerd.
Mission accomplished!
Na het openen van de MyFirstPlugin assembly, zien we de plugin die we hebben gemaakt. We selecteren deze en klikken op de rechter muisknop. In het context menu kiezen we voor “Register New Step”. Het volgende scherm verschijnt.
We vullen hier de message naam in, in ons geval is dit “Create”. Vervolgens geven we op, op welke entiteit de plug-in wordt geregistreerd. Tenslotte geven we de stage of execution op, in ons geval pre-operation. Na het registeren van de stap ziet het scherm er als volgt uit:
Resumerend In dit artikel heb ik in vogelvlucht laten zien wat er komt kijken bij het ontwikkelen van een plug-in voor Dynamics CRM. In dit artikel ben ik nog niet ingegaan op de OrganizationService en het gebruik van EntityImages. In een volgend artikel zal ik daar verder op ingaan. •
Bas van de Sande
We kunnen nu het accountscherm in CRM openen, en hier een nieuw account gaan opvoeren. Wanneer we het nieuwe account saven, zal de naam door de plug-in wordt gewijzigd in “My first plugin”.
Bas van de Sande heeft zich sinds 1995 gespecialiseerd in softwareontwikkeling en advisering op het Microsoft platform. Hij heeft zich sinds 2005 voornamelijk bezig gehouden met SharePoint en Microsoft.Net. Op dit moment ligt zijn focus op Microsoft CRM en blogt hij zeer actief over zijn ervaringen met CRM. Bas is werkzaam als Senior Software Developer/Architect bij CRM Partners.
magazine voor software development 7
DELPHI
Cary Jensen
Bi-Directional Coupling: Here's One Way to Avoid It It is not unusual for code in one unit to need to interact with code in another unit. For example, you may have a form that is defined in one unit and it needs to use a class defined in another unit. So long as the class is defined in the interface section of the second unit, the form in the first unit can "see" that class simply by using the second unit, specifically by including the second unit in either its interface or implementation section uses clause. This situation is known as uni-directional coupling, and it occurs in almost every Delphi application. For example, in order for a VCL form to include a button, the form must use the Vcl.StdCtrls unit, otherwise it would not be possible for the form to call the constructor of the TButton class in order to instantiate a button. Uni-directional coupling is not bad - it is a necessity. There is another form of coupling called bi-directional coupling, and it is not always so benign. Bi-directional coupling occurs when two units use each other. Understanding the Issue While bi-directional coupling is not always a problem, there are a number of circumstances where it should be avoided at all costs. Specifically, code (classes, pure functions, interfaces, and the like) that can be used in a variety of applications should not be coupled, bidirectionally, to code that is specific to one application. Imagine that you have a form that you use to print reports for a particular application. During the course of your development, you build a utility unit that performs a number of different calculations and transformations on data in a generalized way. During your development you write a couple of routines in this utility unit that perform specific manipulations on one or more reports in the reporting unit. You have just now coupled, bi-directionally, the reports unit to the utility unit. Specifically, the reports unit must use the utility unit, in order to access the symbols declared in the utility unit's interface section, and the utility unit must use the reports unit in order to manipulate some of the reports. If you only use this utility unit in this one application this bi-directional coupling will go unnoticed. However, as soon as you use the utility unit in another application you will discover the downside of bi-directional coupling, which is that you must also use the reports unit in this new application, whether or not you need any of the reports that it defines. In more general terms, when two units are coupled bidirectionally, you cannot use one of those units in another application without also using the other. Depending on the units involved in this bi-directional coupling, the problems that it introduces can be extreme. For example, if you were to couple a utility unit to the main form of an application, you will not be able to use that utility unit in any other applications without
8
MAGAZINE
including every single form from the original application in the new application. Over the years I have seen many cases where the casual bi-directional coupling of an otherwise generic unit meant that the generic unit could not be added to another application without including dozens of unnecessary units from some original application, and the process of removing this coupling can take days or weeks, and requires extensive re-testing and debugging. Note: When using Delphi's smart linker, the symbols that are not explicitly used are generally not compiled into the resulting application. However, their resources are! In other words, when a form appears in a uses clause somewhere in your application, the form's resource (the .dfm file, specifically), is unconditionally linked into your executable. Avoiding Bi-Directional Coupling Using Method Pointers The truth is that code in two or more units must be able to interact. Fortunately, there are a number of techniques that you can employ that permits this interaction without producing bi-directional coupling. In this article I am going to demonstrate one of these - method pointers. A method pointer is a type that can hold a reference to a method of an object. Method pointers are the foundation upon which event handlers are built. Think about if for a minute. The TButton class exposes a property called OnClick of type TNotifyEvent. So long as your form's unit includes the Vcl.StdCtrls unit in its uses clause (or FMX.StdCtrls), the form can assigned an event handler to this property, and the button can call this event even though the form's unit is not in a uses clause of the Vcl.StdCtrls unit (or FMX.StdCtrls unit). Yes, the form's unit is dependent on the unit in which the button is declared, but the reverse is not true. As a result, uni-directional coupling is present, not bi-directional, and you can use the button in almost any application without the button using any of the units with which it must interact. This might seem like a minor point, but when you are designing classes that need to interact with each other, you should give some serious thought as to how they interact. An Example I addressed this situation recently. I was designing a class that I intended to reuse across a number of applications. This class, which
DELPHI
was a type of frame, was created at runtime by a class that owned it. Importantly, this frame class needed to inform its owning class that something about it had changed. One way to do this was to expose a public method on the owning class, and call this method from the frame. The problem with this approach is that the frame would need to have knowledge about the owner, which means that the frame would have to have the owner's class within its scope. This would produce bi-directional coupling, since the owner also has to have knowledge of the frame.
{ Public declarations } property OnUpdate: TNotifyEvent read FOnUpdate write FOnUpdate; property OnUpdateGrid: TGridUpdateEvent read FOnUpdateGrid write FOnUpdateGrid; constructor Create(AOwner: TComponent; Year: Integer); overload; end;
The solution was to expose a method pointer type property on the frame. The owner, after creating an instance of the frame, assigns a method of the appropriate method pointer type to this frame's property. When it comes time for the frame to inform the owner about the state change, the frame merely needs to verify that something has assigned a method pointer to this property, after which the frame calls the method to which this property points.
To begin with, let's ignore the declaration of TGridUpdateEvent, and instead focus on the OnUpdate property of the TFrame descendant. This property is of type TNotifyEvent, a method pointer type declared in the System.Classes unit. Here is the declaration of TNotifyEvent:
I have re-created this scenario in the DynamicFrames project. In this project I have a main form that creates a different instance of a frame for each different year value in a result set. The running main form, populated with frames that were created dynamically at runtime, is shown in Figure 1.
In short, this declaration says that a value of TNotifyEvent type is a procedure that takes a single parameter named Sender of type TObject, and that this procedure is a method of an object.
TNotifyEvent = procedure(Sender: TObject) of object;
So long as the main form of this application assigns a properly declared method to this property, the frame class can invoke that method. Here is the code that performs this task, and it is used as an event handler for the AfterPost, AfterDelete, and AfterInsert properties of the frame's ClientDataSet. procedure TGridFrame.ClientDataSetChanged(DataSet: TDataSet); begin if Assigned(FOnUpdate) then FOnUpdate(Self); end;
Here is the code that executes on the main form to create the various frames that it needs. procedure TForm1.FormCreate(Sender: TObject); var
Fig.1: The main form of the DynamicFrames project
GridFrame: TGridFrame; Year: Integer;
Imagine now that each time there is a change to data displayed in one of the frames I want to perform some task in the main form. Rather than making the two units, the one that declares the form and the other that declares the frame, refer to each other, I exposed a public method on the frame that can be assigned a value by the form. Here is the declaration of the frame class:
CurrentYear, LastYear: Integer; begin CurrentYear := ClientDataSet1.FieldByName('SaleYear').AsInteger; ClientDataSet1.Last; LastYear := ClientDataSet1.FieldByName('SaleYear').AsInteger; ClientDataSet1.First;
type TGridUpdateEvent = procedure (Sender: TObject; Company: string;
while CurrentYear <= LastYear do begin
SalesPerson: string) of ob-
GridFrame := TGridFrame.Create(Self, CurrentYear); GridFrame.Name := 'GridFrame' + IntToStr(CurrentYear);
ject; TGridFrame = class(TFrame)
GridFrame.Parent := FlowPanel1;
DBGrid1: TDBGrid; DataSource1: TDataSource;
GridFrame.ClientDataSet1.CloneCursor(Self.ClientDataSet1, True);
Label1: TLabel;
GridFrame.ClientDataSet1.IndexFieldNames := 'SaleYear';
YearLbl: TLabel;
GridFrame.ClientDataSet1.SetRange([CurrentYear], [Cur-
ClientDataSet1: TClientDataSet;
rentYear]);
procedure ClientDataSetChanged(DataSet: TDataSet);
//Hook up the method pointer GridFrame.OnUpdate := DoUpdate;
private { Private declarations }
GridFrame.OnUpdateGrid := GridUpdated;
FOnUpdate: TNotifyEvent;
inc(CurrentYear);
FOnUpdateGrid: TGridUpdateEvent; FYear: Integer;
end; end;
public
magazine voor software development 9
DELPHI
It's the fifth line from the bottom that hooks up the frame to the form. Now, the frame can inform the form to do something, even though the frame has absolutely no knowledge about the form.
begin MessageLbl.Caption := 'The frame for ' + TGridFrame(Sender).YearLbl.Caption + ' has been updated for ' + Company + ' and sales person ' + SalesPerson;
Here is the implementation of the form's DoUpdate method: end; procedure TForm1.DoUpdate(Sender: TObject); begin MessageLbl.Caption := 'The frame for ' + TGridFrame(Sender).YearLbl.Caption +
Figure 2 shows how this project looks when a value in one of the frames has been modified, and the OnUpdateGrid property of the frame has been hooked up.
' has been updated'; end;
As you can see, the form, which does have knowledge of the frame, can use the frame's public and published (if available) members to do something about the changes that have occurred. In this case, a message is displayed informing the user about in which frame a change has occurred. procedure TGridFrame.ClientDataSetChanged(DataSet: TDataSet); begin if Assigned(FOnUpdateGrid) then FOnUpdateGrid(Self, ClientDataSet1.FieldByName('Company').AsString, ClientDataSet1.FieldByName('SalesPerson').AsString); end;
Fig. 2: The message on the main form is produced by an invocation from the frame
A More Complicated Example The limit to this technique as demonstrated with the OnUpdate property of the frame is that the form can only access features visible from the frame's public or published interface. What if the form needs information that is not visible, such as private or protected data in the frame?
Summary Any application that needs to use the form in the DynamicFrames project will also need to use the unit in which the frame is defined. That's fine, since this form needs the frame in order to perform its task. By comparison, you can add the frame's unit to any application, and if that application does not require the form as well, it does not need the form's unit.
The answer is that you can create a custom method pointer, and parameters of that method pointer can return those otherwise notwithin-scope values in its invocation of the supplied method pointer. An example of this is found in the frame's unit. Referring back to that unit's interface type declaration, here is the custom method pointer type. TGridUpdateEvent = procedure (Sender: TObject; Company: string; SalesPerson: string) of object;
In this case, the name of the company and the sales person making the sale are parameters. Since the ClientDataSet is visible to the form, it would have been possible to read these values from the Client DataSet's Fields property, but in reality, the custom method pointer could pass any data, including that stored in strictly private fields, to the method assigned to this method pointer property. The property that exposes the frame's TGridUpdateEvent is named OnUpdateGrid. Here is the line of code in the form's code that, after creating each instance of the frame, assigns a method pointer to the frame's OnUpdateGrid property. GridFrame.OnUpdateGrid := GridUpdated;
And now, here is the implementation of OnUpdateGrid: procedure TForm1.GridUpdated(Sender: TObject; Company, SalesPerson: string);
10
MAGAZINE
You can download this example using the following URL: http://www.JensenDataSystems.com/DynamicFrames.zip •
Cary Jensen Cary Jensen is Chief Technology Officer of Jensen Data Systems. Since 1988 he has built and deployed database applications in a wide range of industries. Cary is an Embarcadero MVP, a best selling author of more than two dozen books on software development, and holds a Ph.D. in Engineering Psychology, specializing in human-computer interaction. His latest book is Delphi in Depth: ClientDataSets 2nd Edition. You can learn more about this book at http://www.JensenDataSystems/cdsbook2.
.NET
Fanie Reijnders
Docfx helps us make documentation easier for .NET One of the most tedious jobs of being a developer is maintaining documentation, but it shouldn’t be a difficult task. When writing documentation, one doesn’t always want to be concerned about how things look per se, but rather how things are communicated. Having to focus on just writing and not the other stuff is very important. Currently supporting languages C# and VB.NET, Docfx is an API documentation generator for .NET. If you have worked with JSDoc or Sphinx before, you will notice that Docfx is a similar concept. It doesn’t just have the ability to directly read the triple-slash comments in code, but also includes a syntax for deep linking to objects and additional files with extra information. These files are written in a special Markdown format called Docfx Flavored Markdown (DFM) which is completely compatible with GitHub’s Markdown. The documentation generation is based on a pre-defined template supporting the Mustache syntax and is completely customizable.
Note that I have included a sample C# project under the /src directory that contains a bunch of sample code and xml comments.
Using Docfx At the time of writing this article, one can either use the executable directly, from within Visual Studio 2015 or as a command on DNX (.Net Execution Environment). Direct use The quickest way to get started is to get your hands dirty and play around with this awesome tool. After downloading the latest release on GitHub (https://github.com/dotnet/docfx/releases), navigate to your target documentation folder using Command Prompt and initialize a default project: The next step is to actually generate and serve up the documentation using the given configuration:
This will initiate a configuration file called docfx.json inside the target directory.
This will generate and serve up a compiled copy of the documentation as a static HTML site on http://localhost:8080.
magazine voor software development 11
.NET
Using Docfx from within Visual Studio You can include a documentation build task to run every time you build a project in Visual Studio. Simply add the Docfx.build Nuget package:
This will add the necessary toc.yml and docfx.json files to the project for your configuration and the documentation site will be generated inside the _site folder every time you build the project.
You can now also combine it with additional content. Here’s an example of a Hello.md file I’ve added to the root directory:
Using Docfx under the .NET Execution Environment (DNX) To run Docfx as a command for DNX, as a prerequisite; the latest version of the .NET Version Manager (DNVM) and the .NET Utilities (DNU) must be installed. Reference the documentation of ASP.NET Core at http://docs.asp.net for more information. After installing all the prerequisites, we need to make sure to target the right source for installing Docfx as it depends on the latest release version of ASP.NET Core 1.0.0-rc1, by setting the DNX_FEED environment variable. Thereafter we install the Docfx command for ASP.NET Core.
Now that Docfx is a command, we could simply use it to build the documentation site by either executing docfx build (or just docfx) and then serving it up on a local server: To have this page show up in the navigation and be part of the rest of the documentation, we need to also add a toc.yml file containing all the links:
In closing Now that we’ve seen how easy it is to create, generate and maintain documentation for the .NET platform using Docfx, I urge you to go to https://dotnet.github.io/docfx to learn more and start playing around with this great tool. Who knows, it might find itself in your next project!• After running the build & serve command we now get this result
Fanie Reynders Fanie Reynders is a technical consultant, technology evangelist and a Microsoft MVP for Visual Studio and Development Technologies that is obsessed with code, architecture and cool new tech in the cloud. Follow him on https://twitter.com/FanieReynders and http://reynders.co
12
MAGAZINE
DOT NET NUKE
Kornelis Pieters
Mijn eerste indruk van de DNN 8 release De eerste indruk van versie 8 van DNNPlatform. Wat is er en wat kan er? Grote wijzigingen en breaking changes. DNN 8 is beschikbaar! Donderdag 14 januari 2016, met belangstelling, heb ik het webinar van DNNCorp gevolgd over de vernieuwingen van DNN 8: MVC, SPA, JWT. Nuttig en bruikbaar. Maar wanneer is de "echte" versie er nu? Wat schetst mijn verbazing: als ik vrijdagochtend de 15e op kantoor kom, zit er een bericht in mijn mailbox met de aankondiging dat DNN 8 vanaf nu beschikbaar is. Mooi! Direct ga ik kijken wat deze finale versie nu voor ons werken met DNN 8 betekent en daarbij kom ik onderwerpen tegen die ik graag met u deel. Ik heb uit de release notes (die u ook in de Engelse taal terug kunt vinden op de releasepagina van DNN Platform op CodePlex) mijn selectie van wetenswaardigheden gemaakt.
In DNN 8 zitten grote wijzigingen en breaking changes Breaking changes Als u in de afgelopen periode onze blogs over DNN heeft gevolgd, dan herkent u een aantal van de volgende items: • .NET Framework versie 4.5.1 of hoger is vereist Een no-brainer, maar wel even aan denken als u een nieuwe installatie of upgrade uitvoert • Verouderde Admin modules zijn weg uit de installatie (SiteLog, Newsletters, Vendors & Banners) Heeft u ze toch nodig (ik kan mij dat haast niet voorstellen) en wilt u over naar DNN 8 (dat kan ik mij dan weer wél voorstellen), dan zijn ze te vinden in de DNNCommunity-sectie op GitHub. • Verouderde navigatieproviders zijn weg uit de installatie (ASP2Menu, DNNDropDown, DNNMenu, DNNTree, Solpart) Opgeruimd staat netjes denk ik dan maar. • Verouderde functionaliteiten zijn verwijderd (What’s New, Feed Browser, Widget Framework, Getting Started, Content List) Yes! • Oude connection-string (appSetting) weg uit de config Tja, die gebruikten wij al niet meer sinds DNN 5 ☺. • Referentie naar de Telerik-controls in een aparte assembly Dat is lastiger, als je DNN 8 gaat gebruiken met bestaande modules die Telerik-controls of op Telerik gebaseerde DNN-controls benutten, dan moet je een hercompilatie/-build doen met verwijzing naar de nieuwe assembly. Bij ons "breaken" daardoor een aantal bestaande modules. Niet lastig, maar het moet wel even gebeuren. • Met diezelfde 'handicap' (breaking) zijn er ook standaardmodules, voorheen bekend als core-modules, die daar last van hebben: FAQ, Feedback, Iframe, Events en Form and List
Als u die gebruikt en dat ook op DNN 8 wilt doen, ga naar de GitHub-repositories en kijk naar een update die DNN 8-compliant is. En ongetwijfeld geldt dit ook voor modules van leveranciers. • Ondersteuning voor IE 8 is weg Daarin is DNN niet alleen: wat ons (en Microsoft) betreft hadden aan dat lijstje van niet meer ondersteund ook IE 9 en 10 mogen worden toegevoegd. Natuurlijk begrijp ik ook, dat de praktijk bij klanten (om diverse redenen) niet altijd helemaal bij is. Nieuw voor developers Hier beginnen de leuke dingen, van deze vernieuwingen word ik warm: • Support voor SPA- (Single Page Application) en MVC-modules met nieuwe VS2015-templates voor deze moduletypes Beide soorten gebruiken we al, goed dat we dit nu op een formele release kunnen voortzetten. • Nieuwe API voor settings Betrouwbaardere oplossing die minder werk vergt, wie wil dat nu niet? Noot: deze opzet is grotendeels door mijn collega Serge gemaakt en die code hebben we aan DNN gedoneerd. • Admin- en Hostpagina's maken vanuit de install van een module Handig als u het platform met eigen beheerfunctionaliteit uitbreidt. • Een control om CSS- en/of JS-files te onderdrukken of minificeren Handig, want stapeling van deze files kan nog wel eens een onvoorspelbaar gedrag geven, zeker als je componenten van anderen in je oplossing gebruikt. • Default.css kan versies hebben Ook een nuttige verbetering: default.css is niet altijd wenselijk en wij ruimen die nogal eens op, nu kunnen we dat beter beheren. Ook verwacht ik dat hierdoor een upgrade van DNN minder verminking aan de site-skin teweegbrengt door een gewijzigde default.css in die nieuwe versie. • DNN 8 gebruikt nu WebAPI 2.0 Mooi, want dat willen wij ook graag in onze oplossingen doen. • JSON Web Token (JWT) authenticatie-support Hier moet ik nog even aan wennen; wij waren net lekker op weg met oAuth 2.0, maar ik begrijp dat JWT flexibeler is en ook geschikt voor gebruik buiten het framework, bijvoorbeeld voor flexibele en betrouwbare authenticatie van apps. Nu is deze feature nog BETA in deze release, dus ik kan er inderdaad nog even aan wennen.
Een mooie set uitbreidingen waar ik als developer lekker mee uit de voeten kan magazine voor software development 13
DOTNET NUKE Maar er is nog meer! Er is in DNN 8 platform nog veel meer gewijzigd dan hierboven beschreven. Een korte opsomming: • Nieuwe image-handler • Nieuwe default teksteditor (CKEditor) in plaats van de vermaledijde RadEditor • Alle Admin functionaliteit als losse modules (dus die kun je nu uit een eigen webapplicatie weglaten, goed voor performance) • DNN is nu een Web Applicatie Project (WAP) in plaats van een Websiteproject (WSP). Beter, betrouwbaarder, sneller dus... • Page output-caching zit nu ook in het Open Source DNN-platform en niet alleen in de betaalde versie • SMTP ondersteunt nu ook TLS-authenticatie. Essentieel als u met Gmail/Google Apps werkt
Allemaal verschillen die aandacht vragen voordat u naar DNN 8 overstapt, maar niet onoverkomelijk
Platforms Ieder platform heeft inmiddels een laptop, een tablet, een telefoon en een horloge. De verschillen zitten in het aantal leveranciers per platform en in de operating systems. Bij Google het OS van de verschillende devices is duidelijk anders. Bij Apple is het OS van de ipad en de iphone gelijk, maar verschillende van de laptop. Bij Microsoft heeft iedere device inmiddels hetzelfde OS en programmeermodel.
Er is nog meer, veel meer Dit lijkt wel zo'n irritante Amerikaans getinte televisiereclame: "Wacht, er is nog meer, veel meer" Het is niet mijn bedoeling op deze wijze reclame voor DNN te maken, ik ben gewoon enthousiast over deze nieuwe release, voor zover u dat nog niet had opgemaakt uit mijn blog!
Google Met de zaken die ik hiervoor genoemd heb zijn we er vast nog niet. In dat opzicht doen wij in de komende weken nog een extra ontdekkingsreis naar de nieuwe mogelijkheden en features. En een deel van die "features" is ongetwijfeld "unwanted", oftewel: (lastige) bugs. Fouten die nog opgelost moeten worden, zoals wij dat ook in onze programmatuur tegenkomen (ja hoor, niets menselijks is ons vreemd). Ik heb alle vertrouwen dat die bugs opgelost gaan worden en dat we daarmee met DNN 8 een grote stap vooruit gaan. U ook? •
Kornelis Pieters Gepassioneerde ontwikkelaar die nieuwe technieken en/of uitstapjes in het vak niet schuwt. Rauw, soms direct, maar valt in het gebruik best mee.
OPROEP! Lijkt het je leuk een bijdrage te leveren aan (het volgende) SDN magazine? We nodigen je van harte uit om een artikel te schrijven over je vakgebied,over ontwikkelingen en/ of achtergronden. Stuur je kopij naar
[email protected], er zit een auteur in ons allemaal!
14
MAGAZINE
Microsoft
Apple
CLOUD
Henry Been
Veilig beheren van Azure Resources vanuit C# Wanneer je aan de slag gaat met een Azure subscription, is een van de eerste dingen waar je mee te maken krijgt de Azure Portal. In deze portal kun je alles wat je nodig hebt in Azure aanmaken, beheren en ook weer verwijderen. Voorbeelden van dit soort resources zijn een Azure Website of een AzureSql database server. Naast de portal kun je gebruik maken van Powershell, een REST API of een cross-platform commandline om je Azure subscription te beheren. Het komt minder vaak voor dat je vanuit C# je Azure resources wilt beheren. Toch zijn er scenario’s waarin dat noodzakelijk is. Bijvoorbeeld in zogenaamde multitentant architecturen, waar de data van elke gebruiker gescheiden is van die van andere gebruikers. In dat geval wil je bijvoorbeeld vanuit je programmacode automatische een AzureSql Database aanmaken als een nieuwe gebruiker zich registreert. In dit artikel laat ik zien hoe je vanuit een C# applicatie je Azure resources kunt beheren, met een AzureSql database als voorbeeld. De nadruk leg ik hierbij op het gebruik van een certificaat voor authenticatie naar Azure. Hiermee kun je beheerstaken uitvoeren zonder gebruikersnamen of wachtwoorden op te hoeven nemen in broncode en daarmee source control. Tevens demonstreer ik het gebruik van het nieuwe Azure security model: Azure Resource Manager. Azure Service Management vs. Azure Resource Management Als we even meer afstand nemen en bekijken hoe we zaken in Azure kunnen beheren, dan is er veel veranderd het afgelopen jaar. Microsoft heeft hard gewerkt aan het ontwikkelen van een nieuwe portal (Ibiza) naast het bestaande management portal. Er is echter veel meer veranderd dan alleen de gebruikerservaring. De nieuwe portal is gebaseerd op Azure Resource Management (ARM), een nieuwe managementlaag voor de Azure Fabric. ARM is de vervanger van Azure Service Management (ASM). De managementlaag bepaalt de manier waarop je abstracte onderdelen in Azure, je resources, benadert. Een Azure resource is bijvoorbeeld een AzureSql server, een Cloud Service of een Website.
Hetzelfde alles-of-niets scenario geldt voor applicaties die je toegang verleent tot je subscription via een management certificate. Je moet een gebruiker of applicatie dus volledige toegang geven, ook wanneer dit helemaal niet nodig is. De enige manier om autorisaties te beperken bestond uit het aanmaken van meerdere subscriptions. Met ARM adresseert Microsoft deze tekortkoming in de vorm van Role Based Access Management (RBAC). Samen met ARM komt ook een volledig nieuwe management REST API voor Azure beschikbaar. Deze API biedt de mogelijkheid om niet alleen je resources te beheren en groeperen, maar ook wíe welke resources of resourcegroepen mag aanmaken, beheren en verwijderen. De mogelijkheden van deze API komen dan ook terug in de Ibiza portal. In dit artikel wordt gebruik gemaakt van ARM, via Powershell. Dit omdat nog niet alle benodigde functionaliteit beschikbaar is in de Ibiza portal. De gebruikte Powershell versie is ≥ 1.0.0 (https://www. powershellgallery.com/packages/Azure/1.0.0) en de meest recente MSOnline module (https://msdn.microsoft.com/en-us/library/jj151815 .aspx). Azure Service Management
Azure Resource Management
Portal
https://manage.windowsazure.com
https://portal.azure.com
REST API
https://management.core.windows.net
https://management.azure.com
Powershell
Verb-Azurexxxx
Switch-AzureMode (Azure PS < 1.0), Verb-AzureRmxxxx (Azure PS >/ 1.0)
Tabel: Verschillen tussen ASM en ARM
Het grote probleem van ASM was dat het niet mogelijk was om in detail te bepalen welke gebruiker toegang heeft tot welke resources. Elke beheerder die toegevoegd wordt aan een subscription is een co-administrator en kan dus bij alle resources in je subscription.
Een applicatie autoriseren Terug naar het doel: Een C# applicatie schrijven die een AzureSql database aan kan maken. Voordat de applicatie geschreven kan worden, moet hij geregistreerd worden in Azure. Gebruikers en applicaties die toegang hebben tot je subscription onder ARM, worden beheerd in een Azure Active Directory. Elke Azure subscription is geassocieerd met precies één Azure Active Directory waarin de gebruikers zitten welke toegang hebben tot de subscription. Omdat wachtwoorden opslaan in source control vermeden moet worden, kan gebruik worden gemaakt van een certificaat waarmee de applicatie zich kan authentiseren tegen bij Azure Active Directory en op basis van ingestelde privileges toegang krijgt tot de juiste Azure resources. Als uitgangssituatie moet er een Azure Active Directory bestaan met minimaal een gebruiker die ‘Global Admin’ is.
magazine voor software development 15
CLOUD
Eerst wordt een verbinding naar de Azure Active Directory gelegd. Inloggen met de global administrator gebruiker. > Connect-MsolService Nu kan de applicatie in Azure AD geregistreerd worden, hiervoor wordt een Service Principal gebruikt: > New-MsolServicePrincipal -DisplayName DemoApplication Het bovenstaande commando geeft na aanmaken van onze applicatie onder andere het AppPrincipalId terug. Dit is later nog nodig, dus sla het op voor later gebruik > $appPrincipalId = “…” Nu de applicatie is aangemaakt, kan het certificaat aan de applicatie gekoppeld worden: > $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate > $certificate.Import("c:\...\YourApplication.cer") > $binaryCertificate = $certificate.GetRawCertData() > $base64Certificate = [System.Convert]::ToBase64String($binaryCertificate); > New-MsolServicePrincipalCredential -AppPrincipalId $appPrincipalId -Type asymmetric -Value $base64Certificate -Usage verif Standaard wordt bij het aanmaken van de service principal ook een gebruikersnaam en wachtwoord aangemaakt. Dit wordt verder niet gebruikt, dus kan beter verwijderd worden.Vraag de details van de applicatie en zijn credentials op en noteer het KeyId van de symetric key
> Get-MsolServicePrincipalCredential -AppPrincipalId $appPrincipalId
Verwijder vervolgens het eerder aangemaakte symetrische credential wat we niet gaan gebruiken > Remove-MsolServicePrincipalCredential -AppPrincipalId $appPrincipalId -KeyIds ($keyId) De applicatie kan nu authenticeren, maar moet nog rechten krijgen binnen de subscription. Eerst moet er ingelogd worden op Azure > Login-AzureRmAccount Nu kan er een nieuwe resource group ‘Demo’ aangemaakt worden: > New-AzureRmResourceGroup -Name Demo -Location WestEurope 16
MAGAZINE
Contributor is een standaard rol welke alle rechten bevat behalve het recht om anderen rechten toe te kennen. Deze wordt toegekend aan de applicatie voor de nieuwe resourcegroup. In de variable $scope wordt de ResourceId ingevuld die het vorige commando terug gaf. > $scope = "/subscriptions/4...f/resourceGroups/Demo" > New-AzureRoleAssignment -ServicePrincipalName $appPrincipalId -RoleDefinitionName Contributor -Scope $scope
Het gebruik van de ARM API vanuit onze applicatie Nu de applicatie gedefinieerd is en toegang heeft tot de resource group ‘Demo,’ kan er overgestapt worden op C# om vanuit een applicatie een Azure SQL Database aan te maken in deze resource group. Hiervoor moet de applicatie zich authenticeren tegen Azure Active Directory met zijn certifcaat, waarna hij een access token terugkrijgt. Met dit access token kunnen we vervolgens een aanroep naar Azure doen. In het voorbeeld hieronder wordt gebruik gemaakt van de NuGet packages Microsoft.Azure.Management.Sql en Microsoft.IdentityModel.Clients.ActiveDirectory. In de listing hieronder is de C# code te zien die gebruikt kan worden om een nieuwe AzureSqlServer te maken. In het eerste deel worden vijf variabelen ingevuld. De eerste waarde armLocation bevat de context waarvoor een AD token wordt opgehaald. Deze is voor alle management operaties gelijk. Het subscriptionId en tenantId zijn de GUIDs die weergegeven worden bij het uitvoeren van het Get-AzureRmSubscripion Cmdlet. Het clientId wordt gevuld met het eerder opgeslagen AppPrincipalId en tenslotte hebben we de thumbprint van het eerder geregistreerde certificaat nodig om dit op te kunnen halen uit de machine store. (Gebruik geen spaties in de thumbprint en denk om het hidden character wat soms aan het begin of einde van de thumbprint staat bij het kopieren ervan.)
In het tweede deel wordt de authenticatie tegen Azure AD gedaan. Hiervoor worden een aantal classes uit het Microsoft.Identity Model.Clients.ActiveDirectory package gebruikt. Uiteindelijk krijgen we hier een token terug, wat wordt meegegeven aan een SqlManagementClient. Deze class komt uit het Microsoft.Azure.Management.Sql package. Deze class kan gebruikt worden voor management operaties op AzureSql servers en databases. Ten slotte wordt deze class dan ook gebruikt om een server aan te maken in de resource group en uiteindelijk een nieuwe database. Conclusie In dit artikel heb ik gedemonstreerd hoe je vanuit C# code je Azure resources kunt beheren. Dit kan op een veilige manier, zonder wachtwoorden op te hoeven slaan in je code of configuratie door gebruik te maken van certificaten. Ook is het via ARM mogelijk om je applicatie zo min mogelijk rechten te geven om zijn taken uit te voeren. Het is even werk om je applicatie te registeren en authenticatie op basis van
CLOUD
een certificaat op te zetten. Echter, wanneer dit eenmaal gedaan is kun je snel en eenvoudig, volledig geautomatiseerd resources beheren.
ResourceGroupName Location ProvisioningState Tags ResourceId
: : : : :
Demo westeurope Succeeded /subscriptions/4…f/resourceGroups/Demo
Ruwe Powershell PS C:\Users\Henry Been> Connect-MsolService PS C:\Users\Henry Been> New-MsolServicePrincipal -DisplayName DemoApplication The following symmetric key was created as one was not supplied jhbAiJ2DZLvskaR+7p0BYUC68GR3sAHDyZWkwtvECVM= DisplayName ServicePrincipalNames ObjectId AppPrincipalId TrustedForDelegation AccountEnabled Addresses KeyType KeyId StartDate EndDate Usage
: : : : : : : : : : : :
DemoApplication {0b2b0571-66d8-4e2e-8aa2-af5038a9d2cd} 42dc1e1e-74e0-4d34-b66a-defea55f692c 0b2b0571-66d8-4e2e-8aa2-af5038a9d2cd False True {} Symmetric 4d9a6a02-cda9-42ac-93d7-b6fff3d887d4 17-2-2016 13:56:36 17-2-2017 13:56:36 Verify
PS C:\Users\Henry Been> $appPrincipalId = "0b2b0571-66d8-4e2e-8aa2af5038a9d2cd"
PS C:\Users\Henry Been> $scope = "/subscriptions/4…f/resourceGroups/Demo" PS C:\Users\Henry Been> New-AzureRmRoleAssignment -ServicePrincipalName $appPrincipalId -RoleDefinitionName Contributor -Scope $scope RoleAssignmentId : /subscriptions/4...f/resourceGroups/Demo/providers/Microsoft.Authorization/roleAssignments/9248dbbd-25d0-4ea1908fb82e41d14bac Scope : /subscriptions/4…f/resourceGroups/Demo : DemoApplication DisplayName SignInName : RoleDefinitionName : Contributor : b24988ac-6180-42a0-ab88-20f7382dd24c RoleDefinitionId ObjectId : 42dc1e1e-74e0-4d34-b66a-defea55f692c : ServicePrincipal ObjectType
PS C:\Users\Henry Been>
PS C:\Users\Henry Been> $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
Ruwe C#: PS C:\Users\Henry Been> $certificate.Import("C:\Users\Henry Been\OneDrive\SSL Certificaten\armclient.henrybeen.nl.crt") PS C:\Users\Henry Been> $binaryCertificate = $certificate.GetRawCertData() PS C:\Users\Henry Been> $base64Certificate = [System.Convert]::ToBase64String($binaryCertificate) PS C:\Users\Henry Been> New-MsolServicePrincipalCredential -AppPrincipalId $appPrincipalId -TYpe Asymmetric -Value $base64Certificate -Usage verify PS C:\Users\Henry Been> Get-MsolServicePrincipalCredential -AppPrincipalId $appPrincipalId cmdlet Get-MsolServicePrincipalCredential at command pipeline position 1 Supply values for the following parameters: ReturnKeyValues: Type Value KeyId StartDate EndDate Usage
: : : : : :
Type Value KeyId StartDate EndDate Usage
: : : : : :
Asymmetric 5bd435d7-10c5-4900-b609-7e53f7bae023 18-10-2015 03:40:43 17-10-2016 17:55:49 Verify Symmetric 4d9a6a02-cda9-42ac-93d7-b6fff3d887d4 17-2-2016 13:56:36 17-2-2017 13:56:36 Verify
PS C:\Users\Henry Been> $keyId = "4d9a6a02-cda9-42ac-93d7b6fff3d887d4" PS C:\Users\Henry Been> Remove-MsolServicePrincipalCredential AppPrincipalId $appPrincipalId -KeyIds($keyId) PS C:\Users\Henry Been> Environment : Account : TenantId : SubscriptionId : CurrentStorageAccount :
Login-AzureRmAccount AzureCloud
[email protected] 4ace870a-33ad-44bf-bd3a-faa5ee1af0ef 4…f
var var var var var
armLocation = "https://management.core.windows.net/"; subscriptionId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX"; tenantId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX"; clientId = "0b2b0571-66d8-4e2e-8aa2-af5038a9d2cd"; certificateThumbprint = "XXXXXXXXXXXXXXXXXXXXXXXXXXX";
var aadEndpoint = $"https://login.windows.net/{tenantId}"; var context = new AuthenticationContext(aadEndpoint); ; var certificate = GetCertificate(certificateThumbprint); var credentials = new ClientAssertionCertificate(clientId, certificate); var loginResult = context.AcquireToken(armLocation, credentials); var tokenCredentials = new TokenCloudCredentials(subscriptionId, loginResult.AccessToken); var managementClient = new SqlManagementClient(tokenCredentials); managementClient.Servers.CreateOrUpdate("Demo", "someuniqueservername", new ServerCreateOrUpdateParameters { Properties = new ServerCreateOrUpdateProperties { AdministratorLogin = "henry", AdministratorLoginPassword = "Test123test", Version = "2.0" }, Location = "West Europe" });
Henry Been Henry werkt als software architect bij SnelStart. Hij houdt zich bezig met het applicatie landschap en de architectuur van de bij SnelStart ontwikkelde software. Zijn interesse ligt bij het schrijven van onderhoudbare en veranderbare code.
PS C:\Users\Henry Been> New-AzureRmResourceGroup -Name Demo -Location WestEurope
magazine voor software development 17
WEB
Danny van der Kraan
ASP.NET Core 1.0: Goodbye HTML helpers and hello TagHelpers! Synopsis: ASP.NET Core 1.0 [MVC 6] comes with a new exciting feature called TagHelpers. Read on to see why I think we can kiss HTML helpers goodbye. Find the accompanying sourcecode on my GitHub [4]. Please note that MVC 6 will be mentioned in square brackets, because at the moment of writing this it was still called that way, but it is all simply called ASP.Net Core 1.0 now. What are TagHelpers? TagHelpers can be seen as the evolution of HTML helpers which were introduced with the launch of the first MVC framework. To provide context you have to imagine that with classic ASP the only way you could automate the generation of HTML is via custom subroutines. After that ASP.NET came with server controls, with view states as biggest plus, to simulate the look and feel of desktop applications and help with the transition for desktop developers. But we all know what happens when we try to jam squares in to round holes. We had to face the fact that web development is nothing like desktop development. To get in line with proper web development the ASP.NET MVC framework was launched with HTML helpers to automate the HTML output. But HTML helpers never really gelled, especially not with front end developers and designers. One of the main pet peeves was that it made you switch a lot from angle brackets (HTML, CSS) to C# (Razor syntax) during work on views, which made the experience unnecessarily uncomfortable. [MVC 6] wants to address this and some smaller issues by introducing TagHelpers. More on this here [1]. On the view side they totally behave like HTML tags. And this reduces context switching. The easiest way to introduce a TagHelper is to look at the one for the anchor tag. With the HTML helper this would've been done as:
With the anchor TagHelper this would look like:
Side note: Please note that asp- is just a convention, but more on that later. The output rendered in the browser is the same for both:
Side note: Provided the default route has not been altered.
18
MAGAZINE
This should illustrate that a designer for instance is much better acquainted with the TagHelper syntax as it handles just like plain HTML, as opposed to the HTML helper which is almost like calling a method in C#. The hyperlink TagHelper is not the only one provided by [MVC 6]. Let's have a quick run through and start off by how to actually get the TagHelpers in your project. Common TagHelpers [MVC 6] comes with a bag of TagHelpers and to add them to your project you need to add the following line to the dependencies section of the project.json file:
Side note: When you save the file Visual Studio should automatically download and install the appropriate NuGet package(s), but if it doesn't then you can also right click on the project and choose "restore packages" from the context menu or use the command "dnu restore" in the developer's command prompt from the folder where the solution file resides. Then you need to make the view aware that you would like to enable TagHelpers. This is an explicit directive, which is the way you want it! Just add this line of code to the top of your view:
Notice how the directive makes use of the glob notation [2]. The first parameter is the name (or names) of the TagHelper(s) you want to use. By specifying the wildcard (*) you instruct the framework to add all TagHelpers. The second parameter is the assembly name. So here we give the instruction to add all TagHelpers from the Microsoft. AspNet.Mvc.TagHelpers assembly.
Side note: You could also add this directive to a new Razor file introduced with ASP.NET 5 called _ViewImports.cshtml.
WEB
Its main purpose is to provide namespaces which all other views can use. Similar to what the web.config file in the Views folder used to do. You can add this file to any view folder as to have finer granular control over which views have access to what exactly. And it's additive, pretty convenient! You can read more on this file here [6].
and looks at its most basic like:
Once this line is added to your view you'll have access to the following predefined TagHelpers: • Anchor • Cache • Environment • Form • Input • Label • Link • Option • Script • Select • TextArea • ValidationMessage • ValidationSummary
The output would be:
Side note: If you don't see TagHelpers in your IntelliSense you need to add a reference to "Microsoft.AspNet.Tooling.Razor": "1.0.0-rc1-final" in your project.json file.
Side note: As you can see the Tag Helper automatically renders an input element to create the anti-forgery token [7]. There are more options, but I'll write about that separately. Input The input Tag Helper is one of the form elements and could be used to replace the HTML helper EditorFor. Imagine the following class:
Let's briefly run through these Tag Helpers. Anchor Well we've seen this at its simplest form already:
The input Tag Helper looks like:
This renders as: Just the "asp-controller" and "asp-action" attributes. But the anchor TagHelper has much more to offer. • You can add route parameters by using the "asp-route-" prefix (example: asp-route-id = @ViewBag.PatientId). • You can use a named route with the "asp-route" attribute • You can force protocols like https by using for example: asp-protocol="https" (and if you want to do this for a certain domain you use "asp-host") • You can link to a specific part of your page by using "asp-fragment"
The input Tag Helper will be described further another time. Label The label TagHelper is one of the form elements and could be used to replace the HTML helper LabelFor. Imagine the following class:
We'll need a follow up to talk about the anchor Tag Helper separately. Cache Can be a useful Tag Helper for improving your performance by storing cache entries in local memory. This means that anything that will stop the host process will cause the cache entries to be lost. This is something to keep in mind when for instance your Azure instances get scaled. A scale down means losing the cache. But if you bear this in mind it simply works by wrapping any portion of Razor View you want and storing that content in an instance of IMemoryCache. The simplest form looks like:
The label Tag Helper looks like:
This renders as:
Notice how the label Tag Helper is capable of automatically grabbing the value of the Name property of the Display attribute. And that is all there is to the label Tag Helper really. But you can do a lot more cool stuff with this, like having it vary by user, or by route. I'll treat the cache Tag Helper in detail another time. Form The form Tag Helper reads much easier than the form HTML helper
Link, Script and Environment The link and script Tag Helper also support globbing[2]. An interesting example could be:
magazine voor software development 19
WEB
Which would render to the browser as: This tells via globbing and the 'asp-src-include' Tag Helper attribute to include all '.js' files in the 'js' of 'wwwroot'. I've thrown 'bootstrap.js', 'jquery.js' and 'npm.js' in the 'js' folder at 'wwwroot' and therefor it produces the following output: And if the property at the asp-for is of type IEnumerable, then the Tag Helper would automatically render a multi-selectable dropdown.
Pretty convenient right? You can also exclude files, reference files from hosted CDN and provide a fallback, and much more. You can even apply cache busting via the 'asp-append-version' attribute. More on these Tag Helpers in a seperate article.
TextArea The text area Tag Helper is the replacement of the HTML helper 'TextAreaFor' and basically works the same as the input Tag Helper. To show how it works I've expended our 'SomeModel' with the property 'SomeLargeString' and a 'MaxLength' attribute:
Side note: Actually, when you use it exactly like the example above you'll get an error, because bootstrap needs jQuery. You'll want to reference hosted CDNs and provide fall-backs, for example:
You simply write the tag in HTML as follows:
But as I've written above, this deserves a separate article. Let's focus on the environment Tag Helper now instead. This Tag Helper works really well with the script and link Tag Helpers and is therefore included in this paragraph. With ASP.NET Core 1.0 you can set the environment to different stages: Development, Staging, and Production. And with the environment Tag Helper we could include all the unminified .js files in development, while using the minified versions in staging/production. Like this:
This outputs the following HTML to the browser:
It plays along nicely again with the attributes like 'MaxLength', 'Required', etc., providing adequate attributes in HTML as can be seen in the rendered HTML above. ValidationMessage and ValidationSummary The textarea Tag Helper is capable of providing its own validation attributes, like 'data-val-maxlength-max', but it is also possible to explicitly set the validation with the validation message TagHelper:
I've seen it most commonly used like this and I currently only use this Tag Helper for this reason. Select and Option A select is not really useful without options, so therefor they are described together in this section. A select will render as a drop down list and is the equivalent of the HTML helper 'DropDownListFor'. So we have 'SomeModel' with 'SomeString' from our example. And in the 'Index' method of the 'HomeController' we add some options to a ViewBag property, like this:
SomeString could have the 'Required' attribute, like so:
The span will actually render to the browser as:
Don't forget to change the post method in the HomeController: You could use this Tag Helper as follows:
20
MAGAZINE
WEB
Just make a simple Succes.cshtml for when the state of the model is valid. And when the required SomeString is not provided on the post the same page will return with the span rendered as:
But most of the time is prettier to summarize all validation messages and that is where the validation summary Tag Helper comes in:
You simply add the 'asp-validation-summary' to a div tag and it works. The attribute requires a ValidationSummary enumeration which could be 'All', 'ModelOnly' or 'None'. Why you would ever want to choose 'None' is beyond me, just don't add a validation summary then! But the 'ModelOnly' is to only include validations on model level and 'All' is to include both model level and property level. It will render as:
First of all you need to derive from the class TagHelper to make a TagHelper (you could theoretically also implement ITagHelper). With the HtmlTargetElement attribute above the MessageTagHelper class you can specify to which HTML element(s) this TagHelper applies to. The Attributes property is a comma separated list with which you can make certain attributes required. I wanted the MessageValue property required as can be seen in the code above. You can decorate the properties with the attribute HtmlAttributeName to give them a clear name in HTML.
Side note: This attribute is not required as the property's name in camel case will be converted to 'lower kebab case' as a convention (so for example: "message-value"). But in this example we adhere to the 'asp-' convention for TagHelpers without messing up the names of the properties. And when SomeProperty is not provided while it has the 'Required' attribute it'll render as:
These were the provided TagHelpers by [MVC 6], now let's see how you can create one yourself! Custom TagHelpers You can expand existing HTML elements with a TagHelper or you can make your own TagHelper completely from scratch. This practice is called 'authoring TagHelpers' [3]. The first kind will function as simply another attribute on the HTML element like we saw with the HTML Helpers 'asp-action' and 'asp-helper' at the anchor tag. The process of making your own TagHelper is very similar in both cases. Let's first create a TagHelper that you can use as attributes. Attributes As a nice short example we can have a span tag that you could provide a message and a level for how big you want that message displayed, like this:
To make this HTML work I wrote the following code which we'll walk through:
Now you need to override either the 'Process' method or the 'ProcessAsync' method if you expect to handle large files or something. This is an extremely simple TagHelper so the sequential variant will be more than sufficient. You get passed along the TagHelperContext and the TagHelperOutput parameter. The TagHelperContext contains all kinds of information about the element this TagHelper targets. So in our case the concerning span element. I didn't need the parameter in this example, but you'll probably need it as the TagHelper becomes more complex. The TagHelperOutput parameter is used all the time as it is used to transform, append and remove the HTML. The TagName is changed to "div" (to be honest this is just because the h tags are block elements and the span tag is an inline element). The TagMode is set to StartTagAndEndTag to enable you to use the span in HTML as a self-closing element. Then you see some code to deal with the given level, nothing fancy. At the end you can see the content appended to the output's 'Content'. You also have 'PreContent' and 'PostContent', which I didn't need for this simple example. But exploring the TagHelperOutput parameter is really going to help you make complex TagHelpers. When we run this the actual HTML rendered will be:
As have been said you can also create HTML elements completely from scratch. Elements You can also create entire new HTML tags. For this example we'll have
magazine voor software development 21
WEB
a look at Bootstrap styled panels:
Notice for instance how it says 'panel-header'. Why is that not 'panelheader', because the class's name is PanelHeaderTagHelper? Well like properties, which is said earlier, class names also follow the lower kebab case. And because we didn't define another name, it will go by default.
Side note:TagHelper suffix is not required but considered a best practice convention.
It would be better to be able to write:
It is more in line with HTML5's semantic way of coding the layout. It reads better. So let's make it happen. All we need is the following code really:
This time the 'ProcessAsync' method is overridden. It is still not necessary, but I wanted to show you what it looks like. There we use 'TagName' to change the tag to a 'div' tag. And we make sure 'panel' is in the class attribute, so Bootstrap's 'panel' styling can kick in. And you can see this basically in all the ProcessAsync methods. Just changing the class to the appropriate one (panel-header, panel-body, panel-footer). Point of interest though is the HtmlTargetElement attribute. It is there to make sure this TagHelper is used on the right HTML element. It has a ParentTag attribute to tell that it only applies if the 'panel-header' (or body/footer) is in between the 'panel' tag. But this is unfortunately not enough. See header, body and footer all apply to this rule and we would end up with divs that have all three classes. So we also need to tell exactly what HTML tag the TagHelper applies to, to prevent this from happening.
Side note: You can find much more (and better) TagHelper samples on Dave Paquette's GitHub [5]. Conclusion I hope we can all agree that the ideal webpage is written in nothing but HTML and Javascript. TagHelpers remove awkward C# code from our Razor Views making it look more like straight up HTML. This is especially great for the non-programmers, like designers, who just want to design great looking pages in HTML and CSS without being tangled in webs of backend code. But also for the full-stack developer who does enough context switching as is. Thank you for reading and check out the source code for this article on GitHub [4]. Links • TagHelpers vs HTML helpers:http://docs.asp.net/projects/mvc/en/latest/views/tag-helpers/intro.html#tag-helpers-compared-to-html-helpers • Glob notation: https://en.wikipedia.org/wiki/Glob_%28programming%29 • Authoring TagHelpers: http://docs.asp.net/projects/mvc/en/latest/views/tag-helpers/authoring.html • My GitHub: https://github.com/dannyvanderkraan/taghelpers • Dave Paquette's TagHelper samples: https://github.com/dpaquette/TagHelperSamples • _ViewImports.cshtml: http://www.exceptionnotfound.net/the-viewimports-cshtml-file-setting-up-view-namespaces-in-mvc-6/ • Anti-forgery token: http://blog.stevensanderson.com/2008/09/01/prevent-cross-siterequest-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/
Danny van der Kraan
Let's go through the code and start at PanelTagHelper. The TagHelper suffix is discarded and the rest is used as the name for the TagHelper. So in this case the HTML element is called "panel". We use the RestrictChildren attribute above the class declaration to restrict the types of HTML elements that can be used in between the 'panel' tag.
22
MAGAZINE
Danny van der Kraan is a passionate software engineer with a wide area of interest. From desktop applications with WPF MVVM and MSSQL to distributed architectures with NServiceBus, Azure Storage and ASP.NET MVC. Currently working for Sound of Data with cutting edge technologies. Follow these adventures on the blog: https://dannyvanderkraan.wordpress.com/
.NET
Fanie Reijnders
Migrate a .csproj from package.config to project.json As we all know: Nuget is awesome. Until recently, on Nuget 2.0; it uses a “packages.config” file within our C# projects and contains a list of all dependent packages for the project. Although this little XML file is quite handy, things tend to go wrong when more complex scenarios that presents itself. In a C# projects, it might mean changing the .csproj file and sometimes (also) the app.config/web.config file(s) as well as other files. Nuget 3.0 supports a brand new format for the packages manifest, called project.json and it brings way more flexibility and power. Starting from Visual Studio 2015, several project types are already utilizing this technology like Universal Windows Platform managed apps, Portable Class Libraries and ASP.NET Core applications. Although traditional project types don’t (yet) support all the functionality of project.json, they can still leverage at least some advantages that this new package format brings: No changes to .csproj files (preventing merge conflicts), no need to run nuget update (by using the * notation), transitive restore (allowing just the top level dependencies to be defined) and P2P dependency flow, for allowing a top level app to always pick up dependencies from class libraries it depends on; without explicitly installing them. Transitioning from old to new Let’s take a typical class library with a dependency on the Newtonsoft Json package with the following packages.config: <packages> <package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> Adding this reference means that this packages added an explicit reference to newtonsoft.json.dll in the .csproj file located in the net45 folder. The side-effects are that it allows one to edit the packages.config file and re-target the application without re-installing the package.
..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newto nsoft.Json.dll True
Making the change Before continuing, it is advised to make a backup of the packages.config file so you can easily roll back if something goes wrong. 1. From the Package Manage Console, uninstall the package: Uninstall-Package newtonsoft.json –Force 2. Create a project.json file on the root level of the project with the following content: { "dependencies": { "Newtonsoft.Json": "7.0.1" }, "frameworks": { "net45": { } }, "runtimes": { "win": { } } }
Note: I added the Newtonsoft.json dependency, it can be added through editing the file, and relying on intellisense from the editor, or by using the standard Nuget GUI. 3. Reload the solution (there is currently a bug where switching from packages.config to project.json requires a reload of the project). 4. Build or restore packages (Right Click on the Solution level). The project will now use project.json instead and the .csproj file will be a bit cleaner:
Note: You cannot see references in the .csproj file, the references get added at build time, and do not show up in the project. You can see them in the references tree in Visual Studio with a Nuget icon. It’s important to know that project types relying heavily on content packages and XDT transforms won’t work with project.json at the moment. •
magazine voor software development 23
IOT
Roel Hans Bethlehem
Setting Up Visual Studio Code On Ubuntu 14.04 Lts In Hyper-V Since I like programming with the Raspberry Pi and also like Microsoft Visual Studio I thought it would be nice to set up a virtualized Ubuntu 14.04 LTS with Visual Studio Code. This can then serve as the development platform for my Raspberry Pi since it has support for node.js and Python. What do you need: • Hyper-V for Virtualization • The ISO for Ubuntu 14.04 LTS • Visual Studio Code (no need to download as we are using an extra repository to install it using the package manager apt-get) I used the x64 version of Ubuntu so I will need the same x64 version of Visual Studio Code. I assume you have set up Hyper-V and downloaded the ISO for Ubuntu. I am also assuming that you have set up an external switch so your virtual machine can connect to the internet. If not you can set it up in the virtual switch manager. Mine is configured as an external network. In effect the VMs will get an ip address from my router and this enables them to use the internet. Steps: 1. Create a new VM in Hyper-V using the wizard 2. Specify name and location of the virtual machine (VM) 3. Specify the newest VM format. I have not tried with earlier formats 4. Assign memory. I just give it 4 GB but you could probably do with less. 5. Assign networking. I have an external virtual switch so I can connect the VM to the internet. This is necessary for the package manager apt-get to be working and we also want to be able to use npm, the node.js packet manager. 6. Create a new virtual harddisk. 20 GB should be plenty. 7. Specify the path to the Ubuntu 14.04 LTS iso in installation options. 8. Finish the wizard. Now the virtual machine is created, go into settings.
Bump up the number of processors: I have set 4 processors as I have plenty of (virtual) processors on an AMD FX8320. This CPU is an octacore. The reason to assign extra processors is that I could not get video acceleration working on Ubuntu in Hyper-V. If you just have one processor the Ubuntu desktop performs quite slowly. Now we also need to set some boot options as otherwise Ubuntu will not boot. We need to disable secure boot (I think this only applies to 2nd generation VMs): Okay now we are done let’s boot Ubuntu by starting the VM. Choose install Ubuntu.
Now the installer boots up and just accept the defaults but make sure you choose the correct timezone and set the correct keyboard settings. For username set it to something you can remember and set it to login automatically.
Now Ubuntu will install and get yourself some more cafeïne.
Restart and after pressing enter you should be greeted with the Ubuntu desktop.
24
MAGAZINE
IOT
GRUB_CMDLINE_LINUX_DEFAULT=”quiet splash video=hyperv_fb:1920×1080” Press ctrl-x and say Y to save your settings. Now update grub by “typing this in the terminal, then reboot: sudo update-grub sudo reboot You should now be able to run your hyper-v vm with Ubuntu in full screen. Now we should be able to install some Visual Studio Code goodness. After the reboot open up the terminal again (taken from http://askubuntu.com/questions/616075/how-to-install-visual-studiocode-on-ubuntu). Now we will need to do a few things.We will need to update Ubuntu to the latest version We will need to install extras to enable some extra functionality. See: https://technet.microsoft.com/en-us/library/ dn531029.aspx We need to fix the video settings so we can run full screen in Hyper-V
sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make sudo apt-get update sudo apt-get install ubuntu-make Then install Visual Studio Code:
1. Update Ubuntu. Open a terminal in Ubuntu. If you are not familiar with Ubuntu just press the button in the upper left corner and type terminal
umake ide visual-studio-code
Click terminal in applications. Then type the following:
ln -s /usr/bin/nodejs /usr/bin/node
sudo apt-get update sudo apt-get upgrade
See: http://stackoverflow.com/questions/26320901/cannot-install-nodejs-usr-bin-env-node-no-such-file-or-directory You are ready to go now. •
Now Visual Studio Code will be installed and an icon will be added in the launchbar for Visual Studio. Fix problematic node nodejs reference
This will update Ubuntu. 2. Install extensions for the Hyper-V on the virtual machine. In the same terminal type: sudo apt-get install –install-recommends linux-virtual-lts-vivid sudo apt-get install –install-recommends linux-tools-virtual-lts-vivid linux-cloud-tools-virtual-lts-vivid 3. Fix the video settings. Since we want to develop we need to run full screen. To do this we need to set the video manually as display settings in Ubuntu cannot detect the correct settings. In the same terminal type (i am using nano since it is more friendly as vi): sudo nano /etc/default/grub Locate the line that reads: GRUB_CMDLINE_LINUX_DEFAULT=”quiet splash”
Roel Hans Bethlehem My name is Roel Hans Bethlehem and I am working as a SharePoint / .NET Architect for the Nederlandsche Bank in Amsterdam. I live in Hilversum with Claire and my kids Kim and David. We have a cat Shadow and a rabbit called Emma.
And change it to (in my case 1920×1080 is the highest my monitor can handle):
magazine voor software development 25
ALM
Wouter de Kort
Code search in Visual Studio Team Services Zoeken door code. Hoe spannend kan dat zijn? Het klinkt misschien niet spannend maar toch is het iets wat we als ontwikkelaars allemaal doen. Zoeken naar een bepaalde method om te kijken hoe iets werkt, een bestaande oplossing voor je probleem proberen te vinden of een voorbeeld van hoe je een API gebruikt. In Visual Studio kun je vrij eenvoudig door je open solution heen zoeken. Maar wat als je het zoeken naar een hoger niveau wilt tillen? Stel dat je verder wilt kijken naar jouw project en wilt weten of iemand in je organisatie een bepaald probleem heeft opgelost of een API gebruikt. Hoe zou je dat aanpakken? Code Search Code Search is een nieuwe feature binnen Visual Studio Team Services die je hierbij helpt. Code Search stelt je instaat om in één keer door meerdere repositories heen te zoeken. Code Search is beschikbaar als een gratis extension die je kunt installeren vanaf de Visual Studio Marketplace (https://marketplace.visualstudio.com/ items/ms.vss-code-search). Nadat je deze extension hebt geïnstalleerd kun je de search box in VSTS gebruiken om een nieuwe code search te starten. Zelf gebruik ik Code Search steeds vaker. Ik heb bijvoorbeeld een Samples project aangemaakt waarin ik onder andere de source code van Roslyn, ASP.NET, .NET Framework en andere open source projecten heb gezet. Hier kun je daarna eenvoudig doorheen zoeken als je wilt weten hoe iets werkt.
method:analyze* levert AnalyzerForLanguage en AnalyzeControlFlow op. Zoeken op arg:x? geeft resultaten als x1, x2 or xx (allemaal gevonden in de Roslyn code!).
Fig.2: De resultaten van een eenvoudige keyword search
Fig.1: Code Search is te installeren vanaf de Visual Studio Marketplace Figuur 2 laat de zoekresultaten van het keyword analyzer zien als je door de Roslyn code base heen zoekt. De zoek resultaten zijn gegroepeerd per bestand. Op de pagina zie je meteen de inhoud van een file en wordt het keyword waarop je zoekt netjes gehighlight. Zoeken op keyword is nog niet zo heel spannend. De echte kracht van Code Search zie je pas als je de ingebouwde filters gaat gebruiken. Figuur 3 laat zien welke filters je allemaal hebt. Zoeken op class:Analyzer bijvoorbeeld beperkt de resultaten tot alle plekken in de Roslyn code waar een class genaamd Analyzer is gedefinieerd. Zoek je op method:analyze* vind je alle resultaten van methods die beginnen met het woord analyze. Het wildcard teken * matched alle karakters terwijl een ? één karakter matched. Dus zoeken op
26
MAGAZINE
Fig.3: Beschikbare filters tijdens het zoeken
ALM
Naast dat je dus kunt zoeken op code elementen, kun je ook filteren op een specifiek project of pad. Bijvoorbeeld, zoeken op basetype:IDisposable path:*Test* geeft alle elementen terug die IDisposable in de class hiërarchie hebben en ergens in het pad het woord Test. Je kunt ook meerdere statements combineren met de bekende operators AND, OR en NOT. Als je bijvoorbeeld zoekt op basetype :IDisposable NOT path:*Test*, worden juist alle resultaten met ergens Test in het pad genegeerd. Je kunt al deze filters zelf invoeren maar je kunt ook de checkboxes gebruiken in de interface. Het filter wordt dan voor je opgebouwd. Momenteel begrijpt Code Search C#, C en C++. Andere talen wordt nog aan gewerkt. Tot slot Met Code Search kun je dus eenvoudig door meerdere repositories zoeken zolang je maar lees rechten hebt op een repository. Microsoft zelf gebruikt dit ook. Zij hebben daarom als beleid dat alle code door iedereen te doorzoeken is. Je kunt voorstellen wat een enorme hoeveelheid aan code dat moet zijn en hoe handig het is om daar zo snel doorheen te kunnen zoeken. Een voorbeeld waarin ze dat gebruikt hebben is bij het hernoemen van Visual Studio Online naar Visual Studio Team Services.
Code Search is nu al beschikbaar voor VSTS en zal ook in een van de updates voor on-premises verschijnen. Veel zoekplezier! •
Wouter de Kort Wouter de Kort werkt als Principal Consultant Microsoft bij Ordina. Hij leidt hier het Application Lifecycle Management Competence Center. Wouter helpt organisaties om voorop te lopen op technisch vlak. Hij richt zich hierbij vooral op ALM, Agile en DevOps. Wouter heft een paar boeken geschreven en is een van de Microsoft ALM Rangers. Je kunt hem vinden op Twitter (@wouterdekort), op zijn blog: http://wouterdekort.com en op de verschillende conferenties waar Wouter spreekt.
Global Azure Bootcamp: 16th of April 216 at a location near you! Building on the exceptional success of previous years, Global Azure Bootcamp 2016 (#GlobalAzure) is a free one-day training event, taking place on the 16th of April 2016 in several venues worldwide, driven by local Microsoft Azure community enthusiasts and experts. It consists of a day of sessions and labs based on the Microsoft Azure Readiness Kit or custom content. The event has been originally designed by 5 Microsoft Azure MVPs in order to benefit the local community members and teach essential Microsoft Azure skills and know-how. While supported by several sponsors, including Microsoft, the event is completely independent and community-driven. http://global.azurebootcamp.net/
“In our first year we had 9000 servers rendering 3D images. In 2014 we’ve smashed that number with 17.000 cores doing diabetes research. The enthusiasm shared by people participating in this one day global event is truly fantastic!” - Maarten Balliauw, global event organizer
magazine voor software development 27