Sunday 9 October 2016

ASP.NET Core 1.0 MVC TagHelper

Tag Helpers are special feature to enable server side code to create, update and render HTML in view file. They are said to be evaluation of Html Helpers. Although, Html Helpers do exist, yet Tag Helpers are very popular as they are just additional attributes in existing Html elements and are very easy for UI designers or front stack developers to understand.
Tag Helpers are attached to HTML tags without effecting raw view. On the other hand, HTML Helpers are methods which are called to yield required HTML. Furthermore, Tag Helper has powered by IntelliSense in Visual Studio, while HTML Helper does not support IntelliSense much. In nutshell, Tag Helpers are efficient, easier to code and make code cleaner with respect to HTML Helper along with many features out of box. Generally all Tag Helpers has use "asp-" attributes to specify special values, for example:
  • asp-controller: is used to specify controller to be used.
  • asp-action: is used to specify action method to be used.
  • asp-area: is used to specify area to be used.
  • asp-for: is used to specify property to be used. Generally, this property is bond to specified control. 
  • asp-route: is used to specify named route instead of controller and action method. 
  • asp-validation-for: is used to specify property to be validated.
  • asp-validation-summary:is used to specify mode of validation summary to be used.
  • asp-antiforgery: is used to enable or disable antiforgery.
  • asp-append-version: is used to add version number to resource, so if there is any change in resource then version number is also changed. It ensures that in case of any change one resource is not used from cache.

ASP.NET Core provides a set of predefined Tag Helpers in Microsoft.AspNetCore.Mvc.TagHelpers. This namespace provides us following important Tag Helpers:
  • AnchorTagHelper
  • FormTagHelper
  • ImageTagHelper
  • InputTagHelper
  • LabelTagHelper
  • LinkTagHelper
  • OptionTagHelper
  • ScriptTagHelper
  • SelectTagHelper
  • TextAreaTagHelper
  • ValidationMessageTagHelper
  • ValidationSummaryTagHelper
  • CacheTagHelper
  • DistributedCacheTagHelper
  • EnvironmentTagHelper

AnchorTagHelper

AnchorTagHelper is used with a tag. It allows data element to be mapped a tag through asp-area, asp-controller, asp-action, asp-route to specify area, controller, action method and route. We can also pass parameters asp-route- construct.

 <a asp-controller="Contact" asp-action="GetContact" asp-route-id="1">Get Details</a> 

FormTagHelper

FormTagHelper is used with form tag. It allows to specify controller, action method. It also allows to specify route name instead of controller and action. Furthermore, it provides services to handle cross-site request forgery. It is important to remember that on submit action form uses name fields of inner objects to create response body.

 <form asp-controller="Contact" asp-action="CreateContact" asp-antiforgery="true" method="post">  
   ...  
 </form>  

ImageTagHelper

ImageTagHelper is used with img tag. It allows to specify asp-append-version, which is used to address image cache problem.

 <img asp-append-version="true" src="/logo.png" alt="My ORganization" /> 

InputTagHelper

InputTagHelper is used with input tag. It allows data element to be mapped input tag through asp-for attribute. While input tag type is decided through data element type and data annotation. While data validation rules are applied through data annotation.
For example element data type Boolen will make type="checkbox", String will make type="text", DateTime will make type="datetime"and Byte, Integer, Single, Double will make type="number". Similarly data annotation EmailAddress will make type="email" , Url will make type="url", HiddenInput will make type="hidden", Phone will make type="tel", Password will make type="password", Date will make type="date"and Time will make type="time".

 <input asp-for="Name" class="form-control" />  

LabelTagHelper

LabelTagHelper is used with label tag. It allows data element to be mapped input tag through asp-for attribute. It uses Display data annotation value of specified data element to display label. If Display attribute has not been applied then element name is used.

 <label asp-for="Name" class="col-md-2 control-label"></label>  

LinkTagHelper

LinkTagHelper is used with link tag. It allows to control link behavior, for example specify and control source and fallback source.

 <link asp-append-version="true" /link>

OptionTagHelper

OptionTagHelper is used with option tag in select tag. It allows to manipulate option elements individually by SelectTagHelper.

 <select asp-for="Title" asp-items="Model.Titles"></select>

ScriptTagHelper

ScriptTagHelper is used with script tag. It allows to control script block behavior, for example specify and control source and fallback source.

 <script asp-append-version="true">
    ...
 </script>

SelectTagHelper

SelectTagHelper is used with select tag. It allows data element to be mapped select tag and option through asp-for and asp-items attributes, asp-for is used to specify selected value element and asp-items is used to specify list to be bounded with options. While data validation rules are applied through data annotation.

 <select asp-for="Title" asp-items="Model.Titles"></select>

TextAreaTagHelper

TextAreaTagHelper is used with textarea tag. It allows data element to be mapped textarea tag through asp-for attribute. While data validation rules are applied through data annotation.

 <textarea asp-for="Description" rows="5" cols="30" />

ValidationMessageTagHelper

ValidationMessageTagHelper is used with span tag. It allows validation messages mapped to span tag through asp-validation-for attribute.

 <span asp-validation-for="Name" class="text-danger" />

ValidationSummaryTagHelper

ValidationSummaryTagHelper is used with div tag. It allows all validation messages mapped to div tag filtered through asp-validation-summary attribute. Possible values for asp-validation-summary as: All, ModelOnly, None.

 <div asp-validation-summary="ModelOnly" class="text-danger"></div>

CacheTagHelper

CacheTagHelper is used to cache content for any section of a view. It is not applied to any standard HTML tags, rather than, it is a server side control. It uses MemoryCache to store.
Please refer to In Memory Caching for more details. It allows us to control cache expiry as per requirement with following attributes:

  • expires attribute allow to specify: 
    • Time interval after which cached data will expire using expires-after.
    • Time interval after which cached data will expire if not used using expires-sliding.
    • Fixed time interval on which cached data will expire using expires-on.

 <cache expires-after="@TimeSpan.FromSeconds(100)">
    ...
 </cache>
  • vary-by attribute allows to specify cache data:
    • Based on some key or model data using vary-by.
    • Based on user using vary-by-user
    • Based on cookie using vary-by-cookie
    • Based on some header value using vary-by-header
    • Based on some query string attribute using vary-by-query
    • Based on route using vary-by-route

 <cache vary-by-user="true">
    ...
 </cache>
  • priority allow to specify priority of cached content. When system runs out of memory, it starts clearing cached contents, in such case items are cleared priority wise. We use Microsoft.Extensions.Caching.Memory.CacheItemPriority to specify priority.

 <cache priority="@CacheItemPriority.Low">
    ...
 </cache> 
  • We can use different attributes together to have mixed mode as per our requirements. But it is important to remember that there will be cached copy for each combination and it can lead to use a huge memory.
 <cache expires-sliding="@TimeSpan.FromSeconds(100)"  vary-by-user="true" priority="@CacheItemPriority.Low">
    ...
 </cache> 

DistributedCacheTagHelper

 DistributedCacheTagHelper is used to cache content to distributed cache servers. It is very useful when we want to handle large amount of content or want to ensure that cached data is even available if web application is restarted. There are few requirements for distributed cache:
It is our responsibility to define Unique Cache Key, otherwise content will be overwritten.
This distributed cache service will be registered through Startup.ConfigureServices. If we do not configure any distributed cache server then ASP.NET Core will use default Memory Cache. We can use SqlServerCache or any other Cache like Radis Cache as per requirements. Please refer to Working with a Distributed Cache for more details.

 <distributed-cache name="UniqueCacheKey">
    ...
 </distributed-cache>

EnvironmentTagHelper

EnvironmentTagHelper is a server side Tag Helper which is used to specify different HTML handling to be used for different environment. It becomes very handy when we have to specify different URLs for links and scripts for different environments. Please refer to Working with Multiple Environments for more details. We can find related example in _Layout.cshtml.



 <environment names="Development">
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
 </environment>
 <environment names="Staging,Production">
    <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/css/bootstrap.min.css"
            asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
            asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
 </environment>

Monday 3 October 2016

.NET Core CSV Writer

Let's discuss how to implementation of generic CSV Writer, which may take input any List and return a CSV string or write to specified file if specified. Although, this is a generic C# implementation and can be used in any .NET Framework supporting generics, yet we are going to discuss this with .NET Core. We are going to use .NET Core Console Application from previous discussion Welcome to .NET Core Console Application.

Add new Class in DotNetCore.ConsoleApplication

We are going to add a new class CsvWriter in DotNetCore.ConsoleApplication.
  • Open existing Solution in Visual Studio 2015.
  • Now add a new class CsvWriter.cs.
    • Open Add New Item Screen through DotNetCore.ConsoleApplication Context Menu of Common folder >> Add >> Class >> Installed >> .NET Core >> Class.
    • Name it CsvWriter.cs.
    • Click OK Button. 
  • Add CsvWriter implementation.
    • Write<T> (IList<T> list, bool includeHeader = true)
    • Creates and returns generated CSV. 
    • Write<T> (IList<T> list, string fileName, bool includeHeader = true)
      Creates and returns generated CSV and saves the generated CSV to specified path.
    • CreateCsvHeaderLine
      Creates CSV header line if includeHeader is sent true.
    • CreateCsvLine<T>(T item, PropertyInfo[] properties)
      Creates a CSV line for given type of object.
    • CreateCsvLine(IList<string> list)
      Creates a CSV line for given list of string by joining them delimated by comma.
    • CreateCsvItem
      Adds provided value item to processed list used to create CSV line.
    • CreateCsvStringListItem
      Adds provided string list as single item to processed list used to create CSV line.
    • CreateCsvStringArrayItem
      Adds provided string array as single item to processed list used to create CSV line.
    • CreateCsvStringItem
      Adds provided string value item to processed list used to create CSV line.
    • ProcessStringEscapeSequence
      Processes the provided data to handle double qoutes and comma value. If we do not apply escape sequences then they can croupt data.
    • WriteFile
      Writes the generated CSV data to file.

 public class CsvWriter
 {
    private const string DELIMITER = ",";

    public string Write<T>(IList<T> list, bool includeHeader = true)
    {
        StringBuilder sb = new StringBuilder();

        Type type = typeof(T);

        PropertyInfo[] properties = type.GetProperties();

        if (includeHeader)
        {
            sb.AppendLine(this.CreateCsvHeaderLine(properties));
        }

        foreach (var item in list)
        {
            sb.AppendLine(this.CreateCsvLine(item, properties));
        }

        return sb.ToString();
    }

    public string Write<T>(IList<T> list, string fileName, bool includeHeader = true)
    {
        string csv = this.Write(list, includeHeader);

        this.WriteFile(fileName, csv);

        return csv;
    }

    private string CreateCsvHeaderLine(PropertyInfo[] properties)
    {
        List<string> propertyValues = new List<string>();

        foreach (var prop in properties)
        {
            string formatString = string.Empty;
            string value = prop.Name;

            var attribute = prop.GetCustomAttribute(typeof(DisplayAttribute));
            if (attribute != null)
            {
                value = (attribute as DisplayAttribute).Name;
            }

            this.CreateCsvStringItem(propertyValues, value);
        }

        return this.CreateCsvLine(propertyValues);
    }

    private string CreateCsvLine<T>(T item, PropertyInfo[] properties)
    {
        List<string> propertyValues = new List<string>();

        foreach (var prop in properties)
        {
            string formatString = string.Empty;
            object value = prop.GetValue(item, null);

            if (prop.PropertyType == typeof(string))
            {
                this.CreateCsvStringItem(propertyValues, value);
            }
            else if (prop.PropertyType == typeof(string[]))
            {
                this.CreateCsvStringArrayItem(propertyValues, value);
            }
            else if (prop.PropertyType == typeof(List<string>))
            {
                this.CreateCsvStringListItem(propertyValues, value);
            }
            else
            {
                this.CreateCsvItem(propertyValues, value);
            }
        }

        return this.CreateCsvLine(propertyValues);
    }

    private string CreateCsvLine(IList<string> list)
    {
        return string.Join(CsvWriter.DELIMITER, list);
    }

    private void CreateCsvItem(List<string> propertyValues, object value)
    {
        if (value != null)
        {
            propertyValues.Add(value.ToString());
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private void CreateCsvStringListItem(List<string> propertyValues, object value)
    {
        string formatString = "\"{0}\"";
        if (value != null)
        {
            value = this.CreateCsvLine((List<string>)value);
            propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private void CreateCsvStringArrayItem(List<string> propertyValues, object value)
    {
        string formatString = "\"{0}\"";
        if (value != null)
        {
            value = this.CreateCsvLine(((string[])value).ToList());
            propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private void CreateCsvStringItem(List<string> propertyValues, object value)
    {
        string formatString = "\"{0}\"";
        if (value != null)
        {
            propertyValues.Add(string.Format(formatString, this.ProcessStringEscapeSequence(value)));
        }
        else
        {
            propertyValues.Add(string.Empty);
        }
    }

    private string ProcessStringEscapeSequence(object value)
    {
        return value.ToString().Replace("\"", "\"\"");
    }

    public bool WriteFile(string fileName, string csv)
    {
        bool fileCreated = false;

        if (!string.IsNullOrWhiteSpace(fileName))
        {
            File.WriteAllText(fileName, csv);

            fileCreated = true;
        }

        return fileCreated;
    }
 }

Add Test Model Class in DotNetCore.ConsoleApplication

We are going to add a new class TestVM in DotNetCore.ConsoleApplication.
  • Open existing Solution in Visual Studio 2015.
  • Now add a new class TestVM.cs.
    • Open Add New Item Screen through DotNetCore.ConsoleApplication Context Menu of Common folder >> Add >> Class >> Installed >> .NET Core >> Class.
    • Name it TestVM.cs.
    • Click OK Button. 
  • Add TestVM implementation.
  • Update Program.cs to initialize List<TestVM> with dummy data and call CsvWriter.



 public class TestVM  
 {  
   [Display(Name = "Test Id")]  
   public int TestId { get; set; }  
   [Display(Name = "Name")]  
   public string TestName { get; set; }  
 }  

 public class Program  
 {  
   public static void Main(string[] args)  
   {  
     Console.WriteLine("Welcome to .NET Core Console Application");  
     List<TestVM> tests = new List<TestVM>  
     {  
       new TestVM {TestId=1, TestName="Bill Gates" },  
       new TestVM {TestId=2, TestName="Warren Buffett" },  
       new TestVM {TestId=3, TestName="Amancio Ortega" },  
       new TestVM {TestId=4, TestName="Carlos Slim Helu" }  
     };  
     string fileName = string.Format("{0}\\test.csv", System.AppContext.BaseDirectory);  
     CsvWriter csvWriter = new CsvWriter();  
     csvWriter.Write(tests, fileName, true);  
     Console.WriteLine("{0} has been created.", fileName);  
     Console.ReadKey();  
   }  
 }  

Run Application in Debug Mode

  • Press F5 or Debug Menu >> Start Debugging or Start Console Application Button on Toolbar to start application in debugging mode. It will start application console in debug mode.
  • It will generate test.csv at given path. Therefore at C:\ASP.NET Core\CSV Writer\DotNetCore\ConsoleApplication.NetCore\bin\Debug\netcoreapp1.0.


Sample Source Code

We have placed sample code for this session in ".NET Core CSV Writer_Code.zip" in https://aspdotnetcore.codeplex.com/SourceControl/latest CodePlex repository.

Sunday 2 October 2016

Welcome to .NET Core 1.0 Console Application

Let’s create our first .NET Core Console Application. We assume that we already have Visual Studio 2015 Update 3 and .NET Core 1.0.0 - VS 2015 Tooling Preview 2. If you have not installed .NET Core yet then I may recommend you to follow our initial discussion How to install .NET Core 1.0. Alternatively you can follow Microsoft Official Site directly.

Create a new Solution

We are going to create a new solution. If we don't perform this task then Visual Studio aromatically creates a solution for a project:
  • Open Visual Studio 2015.
  • Open New Project Screen through menu File >> New >> Project.
  • Select Blank Solution through Installed >> Templates >> Other Projects >> Visual Studio Solutions.
  • Name solution as “DotNetCore”. Set suitable location as “C:\ASP.NET Core\Welcome To .NET Core 1.0\”.
  • Click OK Button.
  • It will create a new solution.

Create new Console Application .NET Core

We are going to add a new Console Application.
  • Open Add New Project Screen through Solution Context Menu >> Add >> New Project or File >> New >> Project.
  • Select Class Console Application (.NET Core) through Installed >> Templates >> Visual C# >> .NET Core.
  • Name project as “DotNetCore.ConsoleApplication”.
  • Set suitable location as “C:\ASP.NET Core\Welcome To .NET Core 1.0\DotNetCore\” (selected by default to solution root).
  • Click OK Button.
  • Add Write greetings and ReadKey in Program.cs.
    •  Console.WriteLine("Welcome to .NET Core Console Application"); prints message on console,
    • Console.ReadKey(); holds the program from termination until a key is pressed.

 public class Program  
 {  
   public static void Main(string[] args)  
   {  
     Console.WriteLine("Welcome to .NET Core Console Application");  
     Console.ReadKey();  
   }  
 }  

Run Application in Debug Mode

To run application, we can chose any of following:
  • Press F5 or Debug Menu >> Start Debugging or Start Console Application Button on Toolbar to start application in debugging mode. It will start application console in debug mode.
  • Make project self contained and it will generate exe file which is directly executable. Please refer to Self Contained Deployment for more details.

Application Deployment

.NET Core allows us two methodologies for compilation and deployment as following, these methodologies can be configured and through project.json:
  • Framework Dependent Deployment
  • Self Contained Deployment

Framework Dependent Deployment

Framework Dependent Deployment allows to build and run application as dll, and in this mode application is executed through dotnet.exe. In this case a single distributable is created which is executed system provided runtime components. By default, Visual Studio 2015 uses this mode. If we observe build directory, then it contains dlls and config files only.
To execute application in this mode:
  • Open  command line prompt or Power Shell.
  • Go to output folder containing ConsoleApplication.NetCore.dll. In our example we have path as: C:\ASP.NET Core\Welcome To .NET Core 1.0\DotNetCore\ConsoleApplication.NetCore\bin\Debug\netcoreapp1.0.
  • Execute command dotnet ConsoleApplication.NetCore.dll
  • It will execute application.


 {   
  "version": "1.0.0-*",   
  "buildOptions": {   
   "emitEntryPoint": true   
  },   
  "dependencies": {   
   "Microsoft.NETCore.App": {   
   "type": "platform",   
   "version": "1.0.0"   
   }   
  },   
  "frameworks": {   
   "netcoreapp1.0": {   
   "imports": "dnxcore50"   
   }   
  }   
  }  

Self Contained Deployment

Self Contained Deployment allows to build and distribute application as an exe, which is execuatable directly. In this case an OS dependent disreputable is created which is executed contains all required runtime components. We may have to do few or more configuration changes in project.json in dependencies, runtimes and others as per requirements. In our case we have to just remove "type": "platform" from dependencies and to add runtimes section.  If we observe build directory, then it contains exe along with dlls and config files only.
To execute application in this mode
  • Open  command line prompt or Power Shell and enter ConsoleApplication.NetCore.exe command
  • Or directly double click ConsoleApplication.NetCore.exe in Windows Explorer.
  • It will execute application.

 {  
  "version": "1.0.0-*",  
  "buildOptions": {  
   "emitEntryPoint": true  
  },  
  "dependencies": {  
   "Microsoft.NETCore.App": {  
    "version": "1.0.0"  
   }  
  },  
  "frameworks": {  
   "netcoreapp1.0": {  
    "imports": "dnxcore50"  
   }  
  },  
  "runtimes": {  
   "win81-x64": {}  
  }  
 }   

Please refer to .NET Core Application Deployment for more details. Few important runtimes are:

  • win7-x86
  • win7-x64
  • win81-x86
  • win81-x64
  • win10-x86
  • win10-x64
  • osx.10.10-x64

Sample Source Code

We have placed sample code for this session in "Welcome To .NET Core Console Application_Code.zip" and "Welcome To .NET Core Console Application_SelfContained_Code.zip" in https://aspdotnetcore.codeplex.com/SourceControl/latest CodePlex repository.