Tag Archives: HTML

JavaScript, CSS, HTML & Other Static Files in ASP .NET Core 3.1

By Shahed C on March 9, 2020

This is the tenth of a new series of posts on ASP .NET Core 3.1 for 2020. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2020, titled ASP .NET Core A-Z! To differentiate from the 2019 series, the 2020 series will mostly focus on a growing single codebase (NetLearner!) instead of new unrelated code snippets week.

Previous post:

NetLearner on GitHub:

In this Article:

J is for JavaScript, CSS, HTML & Other Static Files

NOTEthis article will teach you how to include and customize the use of static files in ASP .NET Core web applications. It is not a tutorial on front-end web development.

If your ASP .NET Core web app has a front end – whether it’s a collection of MVC Views or a Single-Page Application (SPA) – you will need to include static files in your application. This includes (but is not limited to): JavaScript, CSS, HTML and various image files.

When you create a new web app using one of the built-in templates (MVC, Razor Pages or Blazor), you should see a “wwwroot” folder in the Solution Explorer. This points to a physical folder in your file system that contains the same files seen from Visual Studio. However, this location can be configured, you can have multiple locations with static files, and you can enable/disable static files in your application if desired. In fact, you have to “opt in” to static files in your middleware pipeline.

ASP .NET Core 3.1 middleware pipeline diagram
ASP .NET Core 3.1 middleware pipeline diagram

You can browse the (template-generated) sample app (with static files) on GitHub at the following location:

Configuring Static Files via Middleware

Let’s start by observing the Startup.cs configuration file, from any of the web projects. We’ve seen this file several times throughout this blog series. In the Configure() method, you’ll find the familiar method call to enable the use of static files.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
   app.UseStaticFiles();
   ...
}

This call to app.UseStaticFiles() ensures that static files can be served from the designated location, e.g. wwwroot. In fact, this line of code looks identical whether you look at the Startup.cs file in an MVC, Razor Pages or Blazor project.

It’s useful to note the placement of this line of code. It appears before app.UseRouting() and app.UseEndpoints(), which is very important. This ensures that static file requests can be processed and sent back to the web browser without having to touch the application routing middleware. This becomes even more important when authentication is used.

Take a look at any of the web projects to observe the authentication added to them. In the code below, you can see the familiar call to app.UseStaticFiles() once again. However, there is also a call to app.UseAuthentication(). It’s important for the authentication call to appear after the call to use static files. This ensure that the authentication process isn’t triggered when it isn’t needed.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
   app.UseStaticFiles();
   ...
   app.UseAuthentication();
   ...
   app.UseMvc(...);
}

By using the middleware pipeline in this way, you can “short-circuit” the pipeline when a request has been fulfilled by a specific middleware layer. If a static file has been successfully served using the Static Files middleware, it prevents the next layers of middleware (i.e. authentication, routing, etc) from processing the request.

NOTE: if you need to secure any static files, e.g. private images, you can consider a cloud solution such as Azure Blob Storage to store the files. The files can then be served from within the application, instead of actual static files. You could also serve secure files (from outside the wwwroot location) as a FileResult() object returned from an action method that has an [Authorize] attribute.

Customizing Locations for Static Files

It may be convenient to have the default web templates create a location for your static files and also enable the use of those static files. As you’ve already seen, enabling static files isn’t magic. Removing the call to app.useStaticFiles() will disable static files from being served. In fact, the location for static files isn’t magic either.

In a previous post, we had discussed how the Program.cs file includes a call to CreateDefaultBuilder() to set up your application:

public class Program
{
   ...
   public static IHostBuilder CreateHostBuilder(string[] args) =>
      Host.CreateDefaultBuilder(args)
         .ConfigureWebHostDefaults(webBuilder => 
         {
            webBuilder.UseStartup<Startup>();
         });
}

Behind the scenes, this method call sets the “content root” to the current directory, which contains the “wwwroot” folder, your project’s “web root”. These can both be customized.

Host.CreateDefaultBuilder(args).UseContentRoot("c:\\<content-root>")
  • To change the web root within your content root, you can also configure it while building the host, e.g.
webBuilder.UseWebRoot("<content-root>")

You may also use the call to app.UseStaticFiles() to customize an alternate location to serve static files. This allows you to serve additional static files from a location outside of the designated web root.

...
using Microsoft.Extensions.FileProviders;
using System.IO;
...
public void Configure(IApplicationBuilder app)
{
   ...
   app.UseStaticFiles(new StaticFileOptions
   {
      FileProvider = new PhysicalFileProvider(
         Path.Combine(env.ContentRootPath, "AltStaticRoot")),
         RequestPath = "/AltStaticFiles"
   });
}

Wait a minute… why does it look like there are two alternate locations for static files? There is a simple explanation:

  • In the call to Path.Combine(), the “AltStaticRoot” is an actual folder in your current directory. This Path class and its Combine() method are available in the System.IO namespace.
  • The “AltStaticFiles” value for RequestPath is used as a root-level “virtual folder” from which images can be served. The PhysicalFileProvider class is available in the Microsoft.Extensions.FileProviders namespace.

The following markup may be used in a .cshtml file to refer to an image, e.g. MyImage01.png:

<img src="~/AltStaticFiles/MyImages/MyImage01.png" />

The screenshot below shows an example of an image loaded from an alternate location.

VS 2019 showing image loaded from alternate location
Visual Studio showing image loaded from alternate location

The screenshot below shows a web browser displaying such an image.

Web Browser showing image loaded from alternate location
Web Browser showing image loaded from alternate location

Preserving CDN Integrity

When you use a CDN (Content Delivery Network) to serve common CSS and JS files, you need to ensure that the integrity of the source code is reliable. You can rest assured that ASP .NET Core has already solved this problem for you in its built-in templates. Let’s take a look at the shared Identity pages, e.g. _ValidationScriptsPartial.cshtml in the Razor sample code.

<environment include="Development">
  <script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script> 
</environment>

<environment exclude="Development">
 <script  
src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
    </script> 
</environment>

Right away, you’ll notice that there are two conditional <environment> blocks in the above markup. The first block is used only during development, in which the jQuery Validate file is obtained from your local copy. When not in development (e.g. staging, production, etc), the jQuery Validate file is obtained from a CDN.

You could use an automated hash-generation tool to generate the SRI (Subresource Integrity) hash values, but you would have to manually copy the value into your code. You can try out the newer LibMan (aka Library Manager) for easily adding and updating your client-side libraries.

LibMan (aka Library Manager)

The easiest way to use LibMan is to use the built-in features available in Visual Studio. Using LibMan using the IDE is as easy as launching it from Solution Explorer. Specify the provider from the library you want, and any specific files you want from that library.

If you’ve already read my SignalR article from my 2018 blog series, you may recall the steps to add a client library via LibMan (aka Library Manager):

  • Right-click project in Solution Explorer
  • Select Add | Client-Side Library

In the popup that appears, select/enter the following:

  • Provider: choose from cdnjs, filesystem, unpkg
  • Library search term, e.g. @microsoft/signalr@latest to pick the latest stable version of the SignalR client-side library
  • Files: At a minimum, choose specific files, e.g. signalr.js and/or its minified equivalent
  • Target Location: modify the target location folder tree as desired
Adding a client-side library
Adding a client-side library

For more on LibMan (using VS or CLI), check out the official docs:

In any case, using LibMan will auto-populate a “libman.json” manifest file, which you can also inspect and edit manually. The aforementioned SignalR article also includes a real-time polling web app sample. You can view its libman.json file or the newer NetLearner.Portal app’s libman.json file to observe its syntax for using a SignalR client library.

{
  "version": "1.0",
  "defaultProvider": "unpkg",
  "libraries": [
    {
      "library": "@microsoft/signalr@latest",
      "destination": "wwwroot/lib/signalr/",
      "files": [
        "dist/browser/signalr.js"
      ]
    }
  ]
}

Every time you save the libman.json file, it will trigger LibMan’s restore process. This pulls down the necessary libraries from their specified source, and adds them to your local file system. If you want to trigger this restore process manually, you can always choose the “Restore Client-Side Libraries” option by right-clicking the libman.json file in Solution Explorer.

What About NPM or WebPack?

If you’ve gotten this far, you may be wondering: “hey, what about NPM or WebPack?”

It’s good to be aware that LibMan is a not a replacement for your existing package management systems. In fact, the Single-Page Application templates in Visual Studio (for Angular and React) currently use npm and WebPack. LibMan simply provides a lightweight mechanism to include client-side libraries from external locations.

For more information on WebPack in ASP .NET Core, I would recommend these 3rd-party articles:

References

Forms and Fields in ASP .NET Core 3.1

By Shahed C on February 11, 2020

This is the sixth of a new series of posts on ASP .NET Core 3.1 for 2020. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2020, titled ASP .NET Core A-Z! To differentiate from the 2019 series, the 2020 series will mostly focus on a growing single codebase (NetLearner!) instead of new unrelated code snippets week.

Previous post:

NetLearner on GitHub:

In this Article:

F is for Forms (and Fields)

Before Tag Helpers were available, you would have to use HTML Helper methods to create forms and their elements in a ASP .NET Core views. This meant that your form could look something like this:

@using (Html.BeginForm())
{
   <input />
}

With the introduction of Tag Helpers, you can now make your web pages much more cleaner. In fact, Tag Helpers work with both MVC Views and Razor Pages. The syntax is much simpler:

<form method="post">

This looks like HTML because it is HTML. You can add additional server-side attributes within the <form> tag for additional features.

<form asp-controller="ControllerName" asp-action="ActionMethod" method="post">

In this above example, you can see how the attributes asp-controller and asp-action can be used to specify a specific controller name and action method. When these optional attributes are omitted, the current controller and default action method will be used.

Optionally, you can also use a named route, e.g.

<form asp-route="NamedRoute" method="post">

The asp-route attribute will look for a specific route with the name specified. When the form is submitted via HTTP POST, the action method will then attempt to read the form values via a passed values or bound properties.

In a Controller’s class file within an MVC app, you can set an optional Name for your action method’s Route attribute, as shown below:

[Route("/ControllerName/ActionMethod", Name = "NamedRoute")]
public IActionResult ActionMethod()
{
}

While you won’t find new Tag Helper equivalents for each and every HTML Helper you may have used in the past, you should consider using a Tag Helper wherever possible. You can even create your own custom Tag Helpers as well. For more information on custom Tag Helpers, check out the official documentation:

Tag Helpers for HTML form elements

Below is a list of Tag Helpers with their corresponding HTML form elements:

Input Tag Helper

Let’s say you have a model with a couple of fields:

public class MyModel
{
   public string MyProperty1 { get; set; }
   public string MyProperty2 { get; set; }
}

You can use the following syntax to use an Input Tag Helper with an expression name assigned to the asp-for attribute. This allows you to refer to the properties without requiring the “Model.” prefix in your Views and Pages.

@model MyModel 
...
<!-- Syntax -->
<input asp-for="<Expression Name>" />
...
<!-- Examples -->
<input asp-for="MyProperty1" />
<input asp-for="MyProperty2" />

Corresponding to the Input Tag Helper, there are existing HTML Helpers, with some differences:

  • Html.TextBox: doesn’t automatically set the type attribute
  • Html.TextBoxFor: also doesn’t automatically set the type attribute; strongly typed
  • Html.Editor: suitable for collections, complex objects and templates (while Input Tag Helper is not).
  • Html.EditorFor: also suitable for collections, complex objects and templates; strongly typed

Since Input Tag Helpers use an inline variable or expression in your .cshtml files, you can assign the value using the @ syntax as shown below:

@{
   var myValue = "Some Value";
 }
 <input asp-for="@myValue" />

This will generate the following textbox input field:

<input type="text" id="myValue" name="myValue" value="Some Value" />

To create more specific fields for email addresses, passwords, etc, you may use data-type attributes on your models to auto-generate the necessary fields. These may include one of the following enum values:

  • CreditCard
  • Currency
  • Custom
  • Date
  • DateTime
  • Duration
  • EmailAddress
  • Html
  • ImageUrl
  • MultilineText
  • Password
  • PhoneNumber
  • PostalCode
  • Text
  • Time
  • Upload
  • Url
// For example: 
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }

Note that each attribute can be applied on a field for the view/page generator to infer the data type, but is not used for data validation. For validation, you should use the appropriate validation techniques in your code. We will cover validation in a future blog post, but you can refer to the official docs for now:

Checkboxes

Any boolean field in your model will automatically be turned into a checkbox in the HTML form. There is no extra work necessary to specify that the input type is a “checkbox”. In fact, the generated HTML includes the “checkbox” type automatically, sets the “checked” property if checked and wraps it in a label with the appropriate caption. For example, imagine a boolean field named “IsActive”:

// boolean field in a model class
public bool IsActive { get; set; }
<!-- input field in page/view wrapped in label -->
<label class="form-check-label">
   <input class="form-check-input" asp-for="IsActive" /> 
   @Html.DisplayNameFor(model => model.IsActive)
</label>
<!-- HTML generated for boolean field -->
<label class="form-check-label">
<input 
   class="form-check-input" 
   type="checkbox" 
   checked="checked" 
   data-val="true" 
   data-val-required="The IsActive field is required." 
   id="IsActive" 
   name="IsActive" 
   value="true"> IsActive
</label> 

Hidden Fields

In case you’re wondering how you can generate a hidden <input> field, you can simply use the [HiddenInput] attribute on your hidden field’s property, as shown below. If you wish, you can explicitly set “type=hidden” in your Page/View, but I prefer to set the attribute in the model itself.

// hidden property in model class
[HiddenInput] 
public string SomeHiddenField { get; set; } = "Some Value";
<!-- hidden field in page/view -->
<input asp-for="SomeHiddenField" /> 
<!-- HTML generated for hidden field --> 
<input type="hidden" id="SomeHiddenField" name="SomeHiddenField" value="Some Value"> 

Radio Buttons

For radio buttons, you can create one <input> tag for each radio button option, with a reference to a common field, and a unique value for each radio button. Each input element can be paired with a label to include a proper (clickable) text caption . You can generate these in a loop or from a collection from dynamically generated radio buttons. To avoid reusing the same id for each radio button, you could use a string array of values to concatenate a unique suffix for each radio button id.

// string property and value array in page model class
[BindProperty]
public string ExperienceLevel { get; set; }
public string[] ExperienceLevels = new[] { "Novice", "Beginner", "Intermediate", "Advanced" };
<!-- input fields for radio buttons in page/view -->
@foreach (var experienceLevel in Model.ExperienceLevels)
{
    <input type="radio" asp-for="ExperienceLevel" value="@experienceLevel"
            id="ExperienceLevel@(experienceLevel)" />
    <label for="ExperienceLevel@(experienceLevel)">
        @experienceLevel
    </label>
    <br />
}
<!-- HTML generated for radio buttons --> 

<input type="radio" value="Novice" id="ExperienceLevelNovice" name="ExperienceLevel" />
<label for="ExperienceLevelNovice">
    Novice
</label>
<br />
<input type="radio" value="Beginner" id="ExperienceLevelBeginner" name="ExperienceLevel" />
<label for="ExperienceLevelBeginner">
    Beginner
</label>
<br />
<input type="radio" value="Intermediate" id="ExperienceLevelIntermediate" name="ExperienceLevel" />
<label for="ExperienceLevelIntermediate">
    Intermediate
</label>
<br />
<input type="radio" value="Advanced" id="ExperienceLevelAdvanced" name="ExperienceLevel" />
<label for="ExperienceLevelAdvanced">
    Advanced
</label>
<br />

Textarea Tag Helper

The multiline <textarea> field can be easily represented by a Textarea Tag Helper. This is useful for longer strings of text that need to be seen and edited across multiple lines.

public class MyModel
{
   [MinLength(5)]
   [MaxLength(1024)]
   public string MyLongTextProperty { get; set; }
}

As you would expect, you can use the following syntax to use a Textarea Tag Helper with an expression name assigned to the asp-for attribute.

@model MyModel 
...
<textarea asp-for="MyLongTextProperty"></textarea>

This will generate the following textarea input field:

<textarea 
   data-val="true" 
   data-val-maxlength="The field ... maximum length of '1024'." 
   data-val-maxlength-max="1024" 
   data-val-minlength="The field ... minimum length of '5'." 
   data-val-minlength-min="5" 
   id="MyLongTextProperty" 
   maxlength="1024" 
   name="MyLongTextProperty"
></textarea>

Note that the property name and its attributes are used to create that textarea with the necessary id, name, maxlength and data validation settings.

Corresponding to the Textarea Tag Helper, the existing HTML Helper is shown below:

  • Html.TextAreaFor

Label Tag Helper

The <label> field can be represented by a Label Tag Helper. A label usually goes hand-in-hand with a specific <input> field, and is essential in creating text captions for more accessible web applications. The Display attribute from your model’s fields are used for the label’s displayed text values. (You could use the DisplayName attribute instead and omit the Name parameter, but it limits your ability to use localized resources.)

public class MyModel
{
   [Display(Name = "Long Text")]
   public string MyLongTextProperty { get; set; }
}

You can use the following syntax to use a Label Tag Helper along with an Input Tag Helper.

@model MyModel 
...
<label asp-for="MyLongTextProperty"></label>
<input asp-for="MyLongTextProperty" />

This will generate the following HTML elements:

<label for="MyLongTextProperty">Long Text</label>
<input type="text" id="MyLongTextProperty" name="MyLongTextProperty" value="">

Note that the property name and its attributes are used to create both the label with its descriptive caption and also the input textbox with the necessary id and name.

Corresponding to the Label Tag Helper, the existing HTML Helper is shown below:

  • Html.LabelFor

Select Tag Helper

The <select> field (with its nested <option> fields) can be represented by a Select Tag Helper. This visually represents a dropdown or listbox, from which the user may select one or more options. In your model, you can represent this with a List<SelectListItem> of items, made possible by the namespace Microsoft.AspNetCore.Mvc.Rendering.

...
using Microsoft.AspNetCore.Mvc.Rendering;

public class MyModel
{
   public string MyItem { get; set; }
   
   public List<SelectListItem> MyItems { get; } = new List<SelectListItem>
   {
      new SelectListItem { Value = "Item1", Text = "Item One" },
      new SelectListItem { Value = "Item2", Text = "Item Two" },
      new SelectListItem { Value = "Item3", Text = "Item Three" },
   };
}

You can use the following syntax to use a Select Tag Helper.

@model MyModel 
...
<select asp-for="MyItem" asp-items="Model.MyItems"></select>

Note that the asp-items attribute does require a “Model.” prefix, unlike the asp-for attribute that we have been using so far. This will generate the following HTML:

<select id="MyItem" name="MyItem">
   <option value="Item1">Item One</option>
   <option value="Item2">Item Two</option>
   <option value="Item3">Item Three</option>
</select>

Note that the property name and its attributes are used to create both the dropdown list and also the nested options available for selection. For more customization, optgroups and multiple selections, check out the “Select Tag Helper” section in the Tag Helpers documentation at:

Corresponding to the Select Tag Helper, the existing HTML Helpers are shown below:

  • Html.DropDownListFor
  • Html.ListBoxFor

NetLearner Examples

In the NetLearner repository, you’ll find multiple web projects with various views/pages and controllers where applicable. All models are shared in the SharedLib project.

  • Models: In the “Models” folder of the shared library, you’ll find a LearningResource.cs class (shown below) with some fields we will use to display HTML form elements.
  • Views: Within the “Views” subfolder of the MVC project, the “LearningResources” subfolder contains auto-generated views for the LearningResourcesController’s methods.
  • Controllers: The LearningResourcesController class was auto-generated for the Human model, and then its functionality was extracted into a corresponding service class.
  • Services: The LearningResourceService class was written manually to provide CRUD functionality for the LearningResourcesController, and equivalent Razor/Blazor code.
  • Razor Pages: Within the “LearningResources” subfolder of the Razor Pages project’s Pages folder, the pairs of .cshtml and .cs files make up all the CRUD functionality for LearningResources entities.
  • Blazor Components: Within the “Pages” subfolder of the Blazor project, the LearningResources.razor component contains both its own HTML elements and event handlers for CRUD functionality. The ResourceDetail.razor component is reused by its parent component.
public class LearningResource
{
    public int Id { get; set; }

    [DisplayName("Resource")]
    public string Name { get; set; }


    [DisplayName("URL")]
    [DataType(DataType.Url)]
    public string Url { get; set; }

    public int ResourceListId { get; set; }
    [DisplayName("In List")]
    public ResourceList ResourceList { get; set; }

    [DisplayName("Feed Url")]
    public string ContentFeedUrl { get; set; }

    public List<LearningResourceTopicTag> LearningResourceTopicTags { get; set; }
}

Take a look at the Create and Edit views for the Human class, and you’ll recognize familiar sets of <label> and <input> fields that we discussed earlier.

...
<div class="form-group">
    <label asp-for="Name" class="control-label"></label>
    <input asp-for="Name" class="form-control" />
    <span asp-validation-for="Name" class="text-danger"></span>
</div>
...
[HttpPost]
public async Task<IActionResult> Create(   
Bind("Id,Name,Url,ResourceListId,ContentFeedUrl")] LearningResource learningResource)

... 
[HttpPost]
public async Task<IActionResult> Edit(
int id,    
[Bind("Id,Name,Url,ResourceListId,ContentFeedUrl")] LearningResource learningResource)

The Razor Pages in their respective project handle the same functionality in their own way.

Razor Pages with BindProperty

Compared to MVC views, the newer Razor Pages make it a lot easier to bind your model properties to your HTML forms. The [BindProperty] attribute can be applied to MVC Controllers as well, but is much more effective within Razor Pages.

In the NetLearner repo, you’ll find a Razor web project with multiple subfolders, including Pages and their “code-behind” Page Model files.

  • Pages: Within the “Pages” subfolder, the “LearningResources” subfolder within it contains Razor Pages along with corresponding .cs classes that contain the necessary Get/Post methods.
  • Page Models: In the same LearningResources subfolder, the corresponding Page Models contain the necessary CRUD functionality. The implementation of the CRUD functionality from has been extracted into the Shared Library’s corresponding LearningResourceService service class.

This time, take a look at the Create and Edit pages for the LearningResources set of pages, and you’ll once again recognize familiar sets of <label> and <input> fields that we discussed earlier.

<div class="form-group">
    <label asp-for="LearningResource.Name" class="control-label"></label>
    <input asp-for="LearningResource.Name" class="form-control" />
    <span asp-validation-for="LearningResource.Name" class="text-danger"></span>
</div>

Since there are no controller classes in the Razor web project, let’s take a look at the corresponding C# classes for the Create and Edit pages, i.e. Create.cshtml.cs and Edit.cshtml.cs. In both of these classes, we’ll find the [BindProperty] attribute in use, right after the constructor and before the Get/Post methods.

 [BindProperty]
 public LearningResource LearningResource { get; set; }

This [BindProperty] attribute allows you to declaratively bind the LearningResource class and its properties for use by the HTML form in the corresponding Razor Page. This is an opt-in feature that allows to choose which properties to bind. If you wish, you could alternatively bind all public properties in the class by using the [BindProperties] attribute above the class, instead of above each individual member.

NOTE: By default, a Razor Page’s default methods for HTTP GET and HTTP POST are OnGet() and OnPost() respectively. If you wish to use custom page handlers in your HTML forms, you must create custom methods with the prefix OnPost followed by the name of the handler (and optionally followed by the word Async for async methods)

<!-- buttons with custom page handlers --> 
<input type="submit" asp-page-handler="Custom1" value="Submit 1" />
<input type="submit" asp-page-handler="Custom2" value="Submit 2" />
// action methods in .cs file associated with a Razor Page 
public async Task<IActionResult> OnPostCustom1Async() { } 
public async Task<IActionResult> OnPostCustom2sync() { }

The standard set of Get/Post methods are shown below, from Create.cshtml.cs:

public IActionResult OnGet()
{
   return Page();
}
public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    await _learningResourceService.Add(LearningResource);

    var resourceList = await _resourceListService.Get();
    ViewData["ResourceListId"] = new SelectList(resourceList, "Id", "Name", LearningResource.ResourceListId);
    return RedirectToPage("./Index");
}

Note that the LearningResource entity is passed to the service class (which passes it to the shared DB Context) to add it to the database. If you were to remove the aforementioned [BindProperty] attribute, LearningResource would be null and the save operation would fail. The above approach only opts in to accepting HTTP POST requests. To enable use of BindProperty for HTTP GET requests as well, simply set the optional parameter SupportsGet to true, as shown below.

[BindProperty(SupportsGet = true)]

Blazor Example

The Blazor version of NetLearner also reuses the same shared library for its CRUD functionality and entity models via the shared service classes. However, its front-end web app looks noticeably different. There are no controllers or views. Rather, the .razor files contain HTML elements and the C# code necessary to handle user interaction.

An overall example of the Blazor web project is explained earlier in this series:

After the A-Z series is complete, stay tuned for new content that will explain Blazor’s use of <EditForm> and the use of Input Components to render HTML elements and handle events. For now, check out the official documentation at:

Input Components in Blazor include the following:

  • InputText: renders an <input> element
  • InputTextArea: renders an <textarea> element
  • InputSelect: renders a <select> element
  • InputNumber: renders an <input> element of type=number
  • InputCheckbox: renders an <input> element of type=checkbox
  • InputDate: renders an <input> element of type=date

References

JavaScript, CSS, HTML & Other Static Files in ASP .NET Core

By Shahed C on March 11, 2019

This is the tenth of a series of posts on ASP .NET Core in 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

J is for JavaScript, CSS, HTML & Other Static Files

NOTE: this article will teach you how to include and customize the use of static files in ASP .NET Core web applications. It is not a tutorial on front-end web development.

If your ASP .NET Core web app has a front end – whether it’s a collection of MVC Views or a Single-Page Application (SPA) – you will need to include static files in your application. This includes (but is not limited to): JavaScript, CSS, HTML and various image files.

When you create a new web app using one of the built-in templates (MVC or Razor Pages), you should see a “wwwroot” folder in the Solution Explorer. This points to a physical folder in your file system that contains the same files seen from Visual Studio. However, this location can be configured, you can have multiple locations with static files, and you can enable/disable static files in your application if desired. In fact, you have to “opt in” to static files in your middleware pipeline.

Blog-Diagram-AspNetCore-mw

You can browse the (template-generated) sample app (with static files) on GitHub at the following location:

Web sample app with static files: https://github.com/shahedc/AspNetCoreStaticSample

Configuring Static Files via Middleware

Let’s start by observing the Startup.cs configuration file. We’ve seen this file several times throughout this blog series. In the Configure() method, you’ll find the familiar method call to enable the use of static files.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
   app.UseStaticFiles();
   ...
   app.UseMvc(...);
}

This call to app.UseStaticFiles() ensures that static files can be served from the designated location, e.g. wwwroot. In fact, this line of code looks identical whether you look at the Startup.cs file in an MVC or Razor Pages project.

It’s useful to note the placement of this line of code. It appears before app.UseMvc(), which is very important. This ensures that static file requests can be processed and sent back to the web browser without having to touch the MVC middleware. This becomes even more important when authentication is used.

Take a look at either the MVC or Razor Pages projects that have authentication added to them. In the code below, you can see the familiar call to app.UseStaticFiles() once again. However, there is also a call to app.UseAuthentication(). It’s important for the authentication call to appear after the call to use static files. This ensure that the authentication process isn’t triggered when it isn’t needed.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   ...
   app.UseStaticFiles();
   ...
   app.UseAuthentication();
   ...
   app.UseMvc(...);
}

By using the middleware pipeline in this way, you can “short-circuit” the pipeline when a request has been fulfilled by a specific middleware layer. If a static file has been successfully served using the Static Files middleware, it prevents the next layers of middleware (i.e. authentication, MVC) from processing the request.

NOTE: if you need to secure any static files, e.g. private images, you can consider a cloud solution such as Azure Blob Storage to store the files. The files can then be served from within the application, instead of actual static files. You could also serve secure files (from outside the wwwroot location) as a FileResult() object returned from an action method that has an [Authorize] attribute.

Customizing Locations for Static Files

It may be convenient to have the default web templates create a location for your static files and also enable the use of those static files. As you’ve already seen, enabling static files isn’t magic. Removing the call to app.useStaticFiles() will disable static files from being served. In fact, the location for static files isn’t magic either.

In a previous post, we had discussed how the Program.cs file includes a call to CreateDefaultBuilder() to set up your application:

public class Program
{
   ...
   public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
      WebHost.CreateDefaultBuilder(args)
         .UseStartup<Startup>();
}

Behind the scenes, this method call sets the “content root” to the current directory, which contains the “wwwroot” folder, your project’s “web root”. These can both be customized.

WebHost.CreateDefaultBuilder(args).UseContentRoot("c:\\<content-root>")
WebHost.CreateDefaultBuilder(args).UseWebRoot("public")

You may also use the call to app.UseStaticFiles() to customize an alternate location to serve static files. This allows you to serve additional static files from a location outside of the designated web root.

...
using Microsoft.Extensions.FileProviders;
using System.IO;
...
public void Configure(IApplicationBuilder app)
{
   ...
   app.UseStaticFiles(new StaticFileOptions
   {
      FileProvider = new PhysicalFileProvider(
         Path.Combine(env.ContentRootPath, "AltStaticRoot")),
         RequestPath = "/AltStaticFiles"
   });
}

Wait a minute… why does it look like there are two alternate locations for static files? There is a simple explanation:

  • In the call to Path.Combine(), the “AltStaticRoot” is an actual folder in your current directory. This Path class and its Combine() method are available in the System.IO namespace.
  • The “AltStaticFiles” value for RequestPath is used as a root-level “virtual folder” from which images can be served. The PhysicalFileProvider class is available in the Microsoft.Extensions.FileProviders namespace.

The following markup may be used in a .cshtml file to refer to an image, e.g. MyImage01.png:

<img src="~/AltStaticFiles/MyImages/MyImage01.png" />

The screenshot below shows an example of an image loaded from an alternate location.

code sample for alt images

The screenshot below shows a web browser displaying such an image.

VS2017-AltStaticFile-Browser

You can find the above code in the RazorWebAltStatic sample web app on GitHub:

Preserving CDN Integrity

When you use a CDN (Content Delivery Network) to serve common CSS and JS files, you need to ensure that the integrity of the source code is reliable. You can rest assured that ASP .NET Core has already solved this problem for you in its built-in templates. Let’s take a look at the shared Layout page, e.g. _Layout.cshtml in a Razor Pages web application.

<environment include="Development">
 <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</environment>

<environment exclude="Development">
 <link 
   rel="stylesheet" 
   href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/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"
   crossorigin="anonymous"
   integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
</environment>

Right away, you’ll notice that there are two conditional <environment> blocks in the above markup. The first block is used only during development, in which the bootstrap CSS file is obtained from your local copy. When not in development (e.g. staging, production, etc), the bootstrap CSS file is obtained from a CDN, e.g. CloudFlare.

You could use an automated hash-generation tool to generate the SRI (Subresource Integrity) hash values, but you would have to manually copy the value into your code. You can try out the relatively-new LibMan (aka Library Manager) for easily adding and updating your client-side libraries.

LibMan (aka Library Manager)

The easiest way to use LibMan is to use the built-in features available in Visual Studio. Using LibMan using the IDE is as easy as launching it from Solution Explorer. Specify the provider from the library you want, and any specific files you want from that library.

If you’ve already read my SignalR article from my 2018 blog series, you may recall the steps to add a client library via LibMan (aka Library Manager):

  • Right-click project in Solution Explorer
  • Select Add | Client-Side Library

In the popup that appears, select/enter the following:

  • Provider: choose from cdnjs, filesystem, unpkg
  • Library search term, e.g. @aspnet/signalr@1… pick latest stable if desired
  • Files: At a minimum, choose specific files, e.g. signalr.js and/or its minified equivalent

VS2017-SignalR-ClientLib

For more on LibMan (using VS or CLI), check out the official docs:

In any case, using LibMan will auto-populate a “libman.json” manifest file, which you can also inspect and edit manually. The aforementioned SignalR article also includes a real-time polling web app sample. You can view its libman.json file to observe its syntax for using a SignalR client library.

{
  "version": "1.0",
  "defaultProvider": "unpkg",
  "libraries": [
    {
      "library": "@aspnet/signalr@1.1.0",
      "destination": "wwwroot/lib/signalr/",
      "files": [
        "dist/browser/signalr.js",
        "dist/browser/signalr.min.js"
      ]
    }
  ]
}

Every time you save the libman.json file, it will trigger LibMan’s restore process. This pulls down the necessary libraries from their specified source, and adds them to your local file system. If you want to trigger this restore process manually, you can always choose the “Restore Client-Side Libraries” option by right-clicking the libman.json file in Solution Explorer.

What About NPM or WebPack?

If you’ve gotten this far, you may be wondering: “hey, what about NPM or WebPack?”

It’s good to be aware that LibMan is a not a replacement for your existing package management systems. In fact, the Single-Page Application templates in Visual Studio (for Angular and React) currently use npm and WebPack. LibMan simply provides a lightweight mechanism to include client-side libraries from external locations.

For more information on WebPack in ASP .NET Core, I would recommend these 3rd-party articles:

References

Additional reference for pre-compressing static files, found on Twitter via:

@DotNetAppDevhttps://twitter.com/DotNetAppDev/status/1106299093922533376

 

 

Forms and Fields in ASP .NET Core

By Shahed C on February 13, 2019

This is the sixth of a series of posts on ASP .NET Core for 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

This article will refer to the following sample code on GitHub:

Web Forms And Bindings: https://github.com/shahedc/FormsAndBindings

F is for Forms (and Fields)

Before Tag Helpers were available, you would have to use HTML Helper methods to create forms and their elements in a ASP .NET Core views. This meant that your form could look something like this:

@using (Html.BeginForm())
{
   <input />
}

With the introduction of Tag Helpers, you can now make your web pages much more cleaner. In fact, Tag Helpers work with both MVC Views and Razor Pages. The syntax is much simpler:

<form method="post">

This looks like HTML because it is HTML. You can add additional server-side attributes within the <form> tag for additional features.

<form asp-controller="ControllerName" asp-action="ActionMethod" method="post">

In this above example, you can see how the attributes asp-controller and asp-action can be used to specify a specific controller name and action method. When these optional attributes are omitted, the current controller and default action method will be used.

Optionally, you can also use a named route, e.g.

<form asp-route="NamedRoute" method="post">

The asp-route attribute will look for a specific route with the name specified. When the form is submitted via HTTP POST, the action method will then attempt to read the form values via a passed values or bound properties.

In a Controller’s class file within an MVC app, you can set an optional Name for your action method’s Route attribute, as shown below:

[Route("/ControllerName/ActionMethod", Name = "NamedRoute")]
public IActionResult ActionMethod()
{
}

While you won’t find new Tag Helper equivalents for each and every HTML Helper you may have used in the past, you should consider using a Tag Helper wherever possible. You can even create your own custom Tag Helpers as well. For more information on custom Tag Helpers, check out the official documentation:

Tag Helpers for HTML form elements

Below is a list of Tag Helpers with their corresponding HTML form elements:

Input Tag Helper

Let’s say you have a model with a couple of fields:

public class MyModel
{
   public string MyProperty1 { get; set; }
   public string MyProperty2 { get; set; }
}

You can use the following syntax to use an Input Tag Helper with an expression name assigned to the asp-for attribute. This allows you to refer to the properties without requiring the “Model.” prefix in your Views and Pages.

@model MyModel 
...
<!-- Syntax -->
<input asp-for="<Expression Name>" />
...
<!-- Examples -->
<input asp-for="MyProperty1" />
<input asp-for="MyProperty2" />

Corresponding to the Input Tag Helper, there are existing HTML Helpers, with some differences:

  • Html.TextBox: doesn’t automatically set the type attribute
  • Html.TextBoxFor: also doesn’t automatically set the type attribute; strongly typed
  • Html.Editor: suitable for collections, complex objects and templates (while Input Tag Helper is not).
  • Html.EditorFor: also suitable for collections, complex objects and templates; strongly typed

Since Input Tag Helpers use an inline variable or expression in your .cshtml files, you can assign the value using the @ syntax as shown below:

@{
   var myValue = "Some Value";
 }
 <input asp-for="@myValue" />

This will generate the following textbox input field:

<input type="text" id="myValue" name="myValue" value="Some Value" />

To create more specific fields for email addresses, passwords, etc, you may use data-type attributes on your models to auto-generate the necessary fields. These may include one of the following enum values:

  • CreditCard
  • Currency
  • Custom
  • Date
  • DateTime
  • Duration
  • EmailAddress
  • Html
  • ImageUrl
  • MultilineText
  • Password
  • PhoneNumber
  • PostalCode
  • Text
  • Time
  • Upload
  • Url
// For example: 
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }

Note that each attribute can be applied on a field for the view/page generator to infer the data type, but is not used for data validation. For validation, you should use the appropriate validation techniques in your code. We will cover validation in a future blog post, but you can refer to the official docs for now:

Checkboxes

Any boolean field in your model will automatically be turned into a checkbox in the HTML form. There is no extra work necessary to specify that the input type is a “checkbox”. In fact, the generated HTML includes the “checkbox” type automatically, sets the “checked” property if checked and wraps it in a label with the appropriate caption. For example, imagine a boolean field named “IsActive”:

// boolean field in a model class
public bool IsActive { get; set; }
<!-- input field in page/view wrapped in label -->
<label class="form-check-label">
   <input class="form-check-input" asp-for="IsActive" /> 
   @Html.DisplayNameFor(model => model.IsActive)
</label>
<!-- HTML generated for boolean field -->
<label class="form-check-label">
<input 
   class="form-check-input" 
   type="checkbox" 
   checked="checked" 
   data-val="true" 
   data-val-required="The IsActive field is required." 
   id="IsActive" 
   name="IsActive" 
   value="true"> IsActive
</label>

Hidden Fields

In case you’re wondering how you can generate a hidden <input> field, you can simply use the [HiddenInput] attribute on your hidden field’s property, as shown below. If you wish, you can explicitly set “type=hidden” in your Page/View, but I prefer to set the attribute in the model itself.

// hidden property in model class
[HiddenInput] 
public string SomeHiddenField { get; set; } = "Some Value";
<!-- hidden field in page/view -->
<input asp-for="SomeHiddenField" /> 
<!-- HTML generated for hidden field --> 
<input type="hidden" id="SomeHiddenField" name="SomeHiddenField" value="Some Value">

Radio Buttons

For radio buttons, you can create one <input> tag for each radio button option, with a reference to a common field, and a unique value for each radio button. Each input element can be wrapped in a label to include a proper (clickable) text caption . You can generate these in a loop or from a collection from dynamically generated radio buttons.

// string property in model class
public string ExperienceLevel { get; set; }
<!-- input fields for radio buttons in page/view -->
<label><input asp-for="ExperienceLevel" value="N" type="radio" />Novice</label> 
<label><input asp-for="ExperienceLevel" value="B" type="radio"/>Beginner</label> 
<label><input asp-for="ExperienceLevel" value="I" type="radio" />Intermediate</label> 
<label><input asp-for="ExperienceLevel" value="A" type="radio"/>Advanced</label>
<!-- HTML generated for radio buttons --> 
<label><input value="N" type="radio" id="ExperienceLevel" name="ExperienceLevel">Novice</label>
<label><input value="B" type="radio" id="ExperienceLevel" name="ExperienceLevel">Beginner</label>
<label><input value="I" type="radio" id="ExperienceLevel" name="ExperienceLevel">Intermediate</label>
<label><input value="A" type="radio" id="ExperienceLevel" name="ExperienceLevel">Advanced</label>

Textarea Tag Helper

The multiline <textarea> field can be easily represented by a Textarea Tag Helper. This is useful for longer strings of text that need to be seen and edited across multiple lines.

public class MyModel
{
   [MinLength(5)]
   [MaxLength(1024)]
   public string MyLongTextProperty { get; set; }
}

As you would expect, you can use the following syntax to use a Textarea Tag Helper with an expression name assigned to the asp-for attribute.

@model MyModel 
...
<textarea asp-for="MyLongTextProperty"></textarea>

This will generate the following textarea input field:

<textarea 
   data-val="true" 
   data-val-maxlength="The field ... maximum length of '1024'." 
   data-val-maxlength-max="1024" 
   data-val-minlength="The field ... minimum length of '5'." 
   data-val-minlength-min="5" 
   id="MyLongTextProperty" 
   maxlength="1024" 
   name="MyLongTextProperty"
></textarea>

Note that the property name and its attributes are used to create that textarea with the necessary id, name, maxlength and data validation settings.

Corresponding to the Textarea Tag Helper, the existing HTML Helper is shown below:

  • Html.TextAreaFor

Label Tag Helper

The <label> field can be represented by a Label Tag Helper. A label usually goes hand-in-hand with a specific <input> field, and is essential in creating text captions for more accessible web applications. The Display attribute from your model’s fields are used for the label’s displayed text values. (You could use the DisplayName attribute instead and omit the Name parameter, but it limits your ability to use localized resources.)

public class MyModel
{
   [Display(Name = "Long Text")]
   public string MyLongTextProperty { get; set; }
}

You can use the following syntax to use a Label Tag Helper along with an Input Tag Helper.

@model MyModel 
...
<label asp-for="MyLongTextProperty"></label>
<input asp-for="MyLongTextProperty" />

This will generate the following HTML elements:

<label for="MyLongTextProperty">Long Text</label>
<input type="text" id="MyLongTextProperty" name="MyLongTextProperty" value="">

Note that the property name and its attributes are used to create both the label with its descriptive caption and also the input textbox with the necessary id and name.

Corresponding to the Label Tag Helper, the existing HTML Helper is shown below:

  • Html.LabelFor

Select Tag Helper

The <select> field (with its nested <option> fields) can be represented by a Select Tag Helper. This visually represents a dropdown or listbox, from which the user may select one or more options. In your model, you can represent this with a List<SelectListItem> of items, made possible by the namespace Microsoft.AspNetCore.Mvc.Rendering.

...
using Microsoft.AspNetCore.Mvc.Rendering;

public class MyModel
{
   public string MyItem { get; set; }
   
   public List<SelectListItem> MyItems { get; } = new List<SelectListItem>
   {
      new SelectListItem { Value = "Item1", Text = "Item One" },
      new SelectListItem { Value = "Item2", Text = "Item Two" },
      new SelectListItem { Value = "Item3", Text = "Item Three" },
   };
}

You can use the following syntax to use a Select Tag Helper.

@model MyModel 
...
<select asp-for="MyItem" asp-items="Model.MyItems"></select>

Note that the asp-items attribute does require a “Model.” prefix, unlike the asp-for attribute that we have been using so far. This will generate the following HTML:

<select id="MyItem" name="MyItem">
   <option value="Item1">Item One</option>
   <option value="Item2">Item Two</option>
   <option value="Item3">Item Three</option>
</select>

Note that the property name and its attributes are used to create both the dropdown list and also the nested options available for selection. For more customization, optgroups and multiple selections, check out the “Select Tag Helper” section in the Tag Helpers documentation at:

Corresponding to the Select Tag Helper, the existing HTML Helpers are shown below:

  • Html.DropDownListFor
  • Html.ListBoxFor

MVC Sample

In the sample repo, you’ll find an MVC web project with various models, views and controllers.

  • Models: In the “Models” folder, you’ll find a Human.cs class (shown below) with some fields we will use to display HTML form elements.
  • Views: Within the “Views” subfolder, the “Human” subfolder contains auto-generated views for the HumanController’s methods, while the “Attr” subfolder contains simple Index and Detail views to be used by the AttrController class.
  • Controllers: The HumanController class was auto-generated for the Human model, while the AttrController class was manually written to illustrate the use of various attributes (e.g. FromQuery, FromRoute, FromForm) within the action methods.
public class Human
{
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Address { get; set; }
   public DateTime DateOfBirth { get; set; }
   public int FavoriteNumber { get; set; }
   public bool IsActive { get; set; }
}

Take a look at the Create and Edit views for the Human class, and you’ll recognize familiar sets of <label> and <input> fields that we discussed earlier.

...
<div class="form-group">
   <label asp-for="FirstName" class="control-label"></label>
   <input asp-for="FirstName" class="form-control" />
   <span asp-validation-for="FirstName" class="text-danger"></span>
</div>
...

To bind these fields, the Create and Edit methods that respond to HTTP POST both use a [Bind] attribute for the Human parameter with specific fields to bind from the model:

[HttpPost]
public async Task<IActionResult> Create(
   [Bind("Id,FirstName,LastName,Address,DateOfBirth,FavoriteNumber,IsActive")] Human human)

... 
[HttpPost]
public async Task<IActionResult> Edit(
   int id, 
   [Bind("Id,FirstName,LastName,Address,DateOfBirth,FavoriteNumber,IsActive")] Human human)

In the AttrController class, you’ll find a different approach to gathering information submitted in an HTML form.

  • FromQuery: The Index() method uses a [FromQuery] attribute which can obtain information from the URL’s QueryString parameters
  • FromRoute: The first Details() method uses a [FromRoute] attribute which can obtain information from route values.
  • FromForm: The second Details() method uses a [FromForm] attribute which links the submitted form fields to the fields of the corresponding model passed to the method.
[HttpGet]
public IActionResult Index([FromQuery] string humanInfo) { }
...
[HttpGet]
public IActionResult Details([FromRoute] int id) { }
...
[HttpPost]
public IActionResult Details([FromForm] Human human) { }
...

The first two methods above can be reached via HTTP GET requests by accessing a URL, while the third one can be reached when submitting the Details form with a submit button. Run the sample application to click around and view its behavior.

Forms-Bindings

 

<!-- From shared _Layout.cshtml, link to Index page -->
<a 
   class="nav-link text-dark" 
   asp-area="" 
   asp-controller="Attr" 
   asp-action="Index" 
   asp-route-humanInfo="John"
>Attributes</a>

<!-- From Index.cshtml, link to Details page -->
<a 
   class="nav-link text-dark" 
   asp-area="" 
   asp-controller="Attr" 
   asp-action="Details" 
   asp-route-id="1"
>View Details</a>
<!-- From Details.cshtml, self-submitting form -->
<form asp-action="Details">
...
<input type="submit" value="Submit" class="btn btn-primary" />
...
</form>

Note that <a> tags have an asp-route-xx attribute that can have any text value appended to the end of it. These route parameters, e.g. id and humanInfo, correspond directly to action method parameters seen in Index() and Details() methods of the AttrController class.

Razor Pages with BindProperty

Compared to MVC views, the newer Razor Pages make it a lot easier to bind your model properties to your HTML forms. The [BindProperty] attribute can be applied to MVC Controllers as well, but is much more effective within Razor Pages.

In the sample repo, you’ll find a Razor web project with multiple subfolders, including Models and Pages.

  • Models: In the “Models” folder of the Razor web project, you’ll find a Human.cs class (shown below) with some fields we will use to display HTML form elements.
  • Pages: Within the “Pages” subfolder, the “Human” subfolder contains auto-generated Razor Pages along with corresponding .cs classes that contain the necessary Get/Post methods.

Here, the HumanModel class is named slightly differently from the aforementioned (MVC) example’s Human class, since the subfolder within Pages is also called Human. (We would have to rename one or the other to avoid a naming conflict.)

 public class HumanModel
 {
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public DateTime DateOfBirth { get; set; }
    public int FavoriteNumber { get; set; }
    public bool IsActive { get; set; }
 }

This time, take a look at the Create and Edit pages for the Human class, and you’ll once again recognize familiar sets of <label> and <input> fields that we discussed earlier.

 <div class="form-group">
    <label asp-for="HumanModel.FirstName" class="control-label"></label>
    <input asp-for="HumanModel.FirstName" class="form-control" />
    <span asp-validation-for="HumanModel.FirstName" class="text-danger"></span>
 </div>

Since there are no controller classes in the Razor web project, let’s take a look at the corresponding C# classes for the Create and Edit pages, i.e. Create.cshtml.cs and Edit.cshtml.cs. In both of these classes, we’ll find the [BindProperty] attribute in use, right after the constructor and before the Get/Post methods.

 [BindProperty]
 public HumanModel HumanModel { get; set; }

This [BindProperty] attribute allows you to declaratively bind the HumanModel class and its properties for use by the HTML form in the corresponding Razor Page. This is an opt-in feature that allows to choose which properties to bind. If you wish, you could alternatively bind all public properties in the class by using the [BindProperties] attribute above the class, instead of above each individual member.

NOTE: By default, a Razor Page’s default methods for HTTP GET and HTTP POST are OnGet() and OnPost() respectively. If you wish to use custom page handlers in your HTML forms, you must create custom methods with the prefix OnPost followed by the name of the handler (and optionally followed by the word Async for async methods)

<!-- buttons with custom page handlers --> 
<input type="submit" asp-page-handler="Custom1" value="Submit 1" />
<input type="submit" asp-page-handler="Custom2" value="Submit 2" />
// action methods in .cs file associated with a Razor Page 
public async Task<IActionResult> OnPostCustom1Async() { } 
public async Task<IActionResult> OnPostCustom2sync() { }

The standard set of Get/Post methods are shown below, from Create.cshtml.cs:

public IActionResult OnGet()
{
   return Page();
}
public async Task<IActionResult> OnPostAsync()
{
   if (!ModelState.IsValid)
   {
      return Page();
   } 
   _context.HumanModel.Add(HumanModel);
   await _context.SaveChangesAsync(); 
   return RedirectToPage("./Index");
}

Note that the HumanModel is passed to the DB Context to add it to the database. If you were to remove the aforementioned [BindProperty] attribute, HumanModel would be null and the save operation would fail. The above approach only opts in to accepting HTTP POST requests. To enable use of BindProperty for HTTP GET requests as well, simply set the optional parameter SupportsGet to true, as shown below.

[BindProperty(SupportsGet = true)]

References

 

 

Universal Apps at NoVA Code Camp 2014

By Shahed C on October 11, 2014

Recently, I presented Universal Apps at Nova Code Camp on Sat Oct 11, 2014. 

Northern Virginia Code Camp 2014

 Presentation Material

Continue reading