Category Archives: Web Development

Handling Errors in ASP .NET Core 3.1

By Shahed C on February 24, 2020

This is the eighth 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:

H is for Handling Errors

Unless you’re perfect 100% of the time (who is?), you’ll most likely have errors in your code. If your code doesn’t build due to compilation errors, you can probably correct that by fixing the offending code. But if your application encounters runtime errors while it’s being used, you may not be able to anticipate every possible scenario.

Runtime errors may cause Exceptions, which can be caught and handled in many programming languages. Unit tests will help you write better code, minimize errors and create new features with confidence. In the meantime, there’s the good ol’ try-catch-finally block, which should be familiar to most developers.

NOTE: You may skip to the next section below if you don’t need this refresher.

try-catch-finally in C#

Exceptions with Try-Catch-Finally

The simplest form of a try-catch block looks something like this:

try
{
   // try something here

} catch (Exception ex)
{
  // catch an exception here
}

You can chain multiple catch blocks, starting with more specific exceptions. This allows you to catch more generic exceptions toward the end of your try-catch code.  In a string of catch() blocks, only the caught exception (if any) will cause that block of code to run.

try
{
   // try something here

} catch (IOException ioex)
{
  // catch specific exception, e.g. IOException

} catch (Exception ex)
{
  // catch generic exception here

}

Finally, you can add the optional finally block. Whether or not an exception has occurred, the finally block will always be executed.

try
{
   // try something here

} catch (IOException ioex)
{
  // catch specific exception, e.g. IOException

} catch (Exception ex)
{
  // catch generic exception here

} finally
{
   // always run this code

}

Try-Catch-Finally in NetLearner

In the NetLearner sample app, the LearningResourcesController uses a LearningResourceService from a shared .NET Standard Library to handle database updates. In the overloaded Edit() method, it wraps a call to the Update() method from the service class to catch a possible DbUpdateConcurrencyException exception. First, it checks to see whether the ModelState is valid or not.

if (ModelState.IsValid)
{
    ...
}

Then, it tries to update user-submitted data by passing a LearningResource object. If a DbUpdateConcurrencyException exception occurs, there is a check to verify whether the current LearningResource even exists or not, so that a 404 (NotFound) can be returned if necessary.

try
{
    await _learningResourceService.Update(learningResource);
}
catch (DbUpdateConcurrencyException)
{
    if (!LearningResourceExists(learningResource.Id))
    {
        return NotFound();
    }
    else
    {
        throw;
    }
}
return RedirectToAction(nameof(Index));

In the above code, you can also see a throw keyword by itself, when the expected exception occurs if the Id is found to exist. In this case, the throw statement (followed immediately by a semicolon) ensures that the exception is rethrown, while preserving the stack trace.

Run the MVC app and navigate to the Learning Resources page after adding some items. If there are no errors, you should see just the items retrieved from the database.

Learning Resources UI (no errors)

If an exception occurs in the Update() method, this will cause the expected exception to be thrown. To simulate this exception, you can intentionally throw the exception inside the update method. This should cause the error to be displayed in the web UI when attempting to save a learning resource.

Exception details in Web UI

Error Handling for MVC

In ASP .NET Core MVC web apps, unhandled exceptions are typically handled in different ways, depending on whatever environment the app is running in. The default template uses the DeveloperExceptionPage middleware in a development environment but redirects to a shared Error view in non-development scenarios. This logic is implemented in the Configure() method of the Startup.cs class.

if (env.IsDevelopment())
{
   app.UseDeveloperExceptionPage();
   app.UseDatabaseErrorPage();  
}
else
{
   app.UseExceptionHandler("/Home/Error");
   ...
}

The DeveloperExceptionPage middleware can be further customized with DeveloperExceptionPageOptions properties, such as FileProvider and SourceCodeLineCount.

var options = new DeveloperExceptionPageOptions
{
   SourceCodeLineCount = 2
};  
app.UseDeveloperExceptionPage(options); 

Using the snippet shown above, the error page will show the offending line in red, with a variable number of lines of code above it. The number of lines is determined by the value of SourceCodeLineCount, which is set to 2 in this case. In this contrived example, I’m forcing the exception by throwing a new Exception in my code.

Customized lines of code within error page

For non-dev scenarios, the shared Error view can be further customized by updating the Error.cshtml view in the Shared subfolder. The ErrorViewModel has a ShowRequestId boolean value that can be set to true to see the RequestId value.

@model ErrorViewModel
@{
   ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>header content</h3>
<p>text content</p> 

In the MVC project’s Home Controller, the Error() action method sets the RequestId to the current Activity.Current.Id if available or else it uses HttpContext.TraceIdentifier. These values can be useful during debugging.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
   return View(new ErrorViewModel { 
      RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier 
   });
} 

But wait… what about Web API in ASP .NET Core? After posting the 2019 versions of this article in a popular ASP .NET Core group on Facebook, I got some valuable feedback from the admin:

Dmitry Pavlov: “For APIs there is a nice option to handle errors globally with the custom middleware https://code-maze.com/global-error-handling-aspnetcore – helps to get rid of try/catch-es in your code. Could be used together with FluentValidation and MediatR – you can configure mapping specific exception types to appropriate status codes (400 bad response, 404 not found, and so on to make it more user friendly and avoid using 500 for everything).”

For more information on the aforementioned items, check out the following resources:

Later on in this series, we’ll cover ASP .NET Core Web API in more detail, when we get to “W is for Web API”. Stay tuned!

Error Handling for Razor Pages

Since Razor Pages still use the MVC middleware pipeline, the exception handling is similar to the scenarios described above. For starters, here’s what the Configure() method looks like in the Startup.cs file for the Razor Pages web app sample.

if (env.IsDevelopment())
{
   app.UseDeveloperExceptionPage();
   app.UseDatabaseErrorPage(); 
}
else
{
   app.UseExceptionHandler("/Error");
   ...
}

In the above code, you can see the that development environment uses the same DeveloperExceptionPage middleware. This can be customized using the same techniques outlined in the previous section for MVC pages, so we won’t go over this again.

As for the non-dev scenario, the exception handler is slightly different for Razor Pages. Instead of pointing to the Home controller’s Error() action method (as the MVC version does), it points to the to the /Error page route. This Error.cshtml Razor Page found in the root level of the Pages folder.

@page
@model ErrorModel
@{
   ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
    <p>
        <strong>Request ID:</strong> <code>@Model.RequestId</code>
    </p>
}

<h3>custom header text</h3>
<p>custom body text</p>

The above Error page for looks almost identical to the Error view we saw in the previous section, with some notable differences:

  • @page directive (required for Razor Pages, no equivalent for MVC view)
  • uses ErrorModel (associated with Error page) instead of ErrorViewModel (served by Home controller’s action method)

Error Handling for Blazor

In Blazor web apps, the UI for error handling is included in the Blazor project templates. Consequently, this UI is available in the NetLearner Blazor sample as well. The _Host.cshtml file in the Pages folder holds the following <environment> elements:

<div id="blazor-error-ui">
     <environment include="Staging,Production">
         An error has occurred. This application may no longer respond until reloaded.
     </environment>
     <environment include="Development">
         An unhandled exception has occurred. See browser dev tools for details.
     </environment>
     <a href="" class="reload">Reload</a>
     <a class="dismiss">🗙</a>
 </div>

The div identified by the id “blazor-error-ui” ensures that is hidden by default, but only shown when an error has occured. Server-side Blazor maintains a connection to the end-user by preserving state with the use of a so-called circuit.

Each browser/tab instance initiates a new circuit. An unhandled exception may terminate a circuit. In this case, the user will have to reload their browser/tab to establish a new circuit.

According to the official documentation, unhandled exceptions may occur in the following areas:

  1. Component instantiation: when constructor invoked
  2. Lifecycle methods: (see Blazor post for details)
  3. Upon rendering: during BuildRenderTree() in .razor component
  4. UI Events: e.g. onclick events, data binding on UI elements
  5. During disposal: while component’s .Dispose() method is called
  6. JavaScript Interop: during calls to IJSRuntime.InvokeAsync<T>
  7. Prerendering: when using the Component tag helper

To avoid unhandled exceptions, use try-catch exception handlers within .razor components to display error messages that are visible to the user. For more details on various scenarios, check out the official documentation at:

Logging Errors

To log errors in ASP .NET Core, you can use the built-in logging features or 3rd-party logging providers. In ASP .NET Core 2.x, the use of CreateDefaultBuilder() in Program.cs takes of care default Logging setup and configuration (behind the scenes).

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

The Web Host Builder was replaced by the Generic Host Builder in ASP .NET Core 3.0, so it looks slightly different now. For more information on Generic Host Builder, take a look at the previous blog post in this series: Generic Host Builder in ASP .NET Core.

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

The host can be used to set up logging configuration, e.g.:

public static IHostBuilder CreateHostBuilder(string[] args) =>
     Host.CreateDefaultBuilder(args)
         .ConfigureLogging((hostingContext, logging) =>
         {
             logging.ClearProviders();
             logging.AddConsole(options => options.IncludeScopes = true);
             logging.AddDebug();
         })
         .ConfigureWebHostDefaults(webBuilder =>
         {
             webBuilder.UseStartup();
         });

To make use of error logging (in addition to other types of logging) in your web app, you may call the necessary methods in your controller’s action methods or equivalent. Here, you can log various levels of information, warnings and errors at various severity levels.

As seen in the snippet below, you have to do the following in your MVC Controller that you want to add Logging to:

  1. Add using statement for Logging namespace
  2. Add a private readonly variable for an ILogger object
  3. Inject an ILogger<model> object into the constructor
  4. Assign the private variable to the injected variable
  5. Call various log logger methods as needed.
...
using Microsoft.Extensions.Logging;

public class MyController: Controller
{
   ...
   private readonly ILogger _logger; 

   public MyController(..., ILogger<MyController> logger)
   {
      ...
      _logger = logger;
   }
   
   public IActionResult MyAction(...)
   {
      _logger.LogTrace("log trace");
      _logger.LogDebug("log debug");
      _logger.LogInformation("log info");
      _logger.LogWarning("log warning");
      _logger.LogError("log error");
      _logger.LogCritical("log critical");
   }
}

In Razor Pages, the logging code will go into the Page’s corresponding Model class. As seen in the snippet below, you have to do the following to the Model class that corresponds to a Razor Page:

  1. Add using statement for Logging namespace
  2. Add a private readonly variable for an ILogger object
  3. Inject an ILogger<model> object into the constructor
  4. Assign the private variable to the injected variable
  5. Call various log logger methods as needed.
...
using Microsoft.Extensions.Logging;

public class MyPageModel: PageModel
{
   ...
   private readonly ILogger _logger;

   public MyPageModel(..., ILogger<MyPageModel> logger)
   {
      ...
      _logger = logger; 
   }
   ...
   public void MyPageMethod() 
   {
      ... 
      _logger.LogInformation("log info"); 
      _logger.LogError("log error");
      ...
   }
} 

You may have noticed that Steps 1 through 5 are pretty much identical for MVC and Razor Pages. This makes it very easy to quickly add all sorts of logging into your application, including error logging.

Transient fault handling

Although it’s beyond the scope of this article, it’s worth mentioning that you can avoid transient faults (e.g. temporary database connection losses) by using some proven patterns, practices and existing libraries. To get some history on transient faults, check out the following article from the classic “patterns & practices”. It describes the so-called “Transient Fault Handling Application Block”.

More recently, check out the docs on Transient Fault Handling:

And now in .NET Core, you can add resilience and transient fault handling to your .NET Core HttpClient with Polly!

You can get more information on the Polly project on the official Github page:

References

Generic Host Builder in ASP .NET Core 3.1

By Shahed C on February 17, 2020

This is the seventh 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:

G is for Generic Host Builder

The Generic Host Builder in ASP .NET Core was introduced in v2.1, but only meant for non-HTTP workloads. However, it has now replaced the Web Host Builder as of v3.0 in 2019.

Generic Host Builder in ASP .NET Core 3.x

History Lesson: Generic Host Builder in 2.x

So, if the Generic Host Builder wasn’t used for web hosting in v2.x, what was it there for? The aforementioned non-HTTP workloads include a number of capabilities according to the 2.2 documentation, including:

  • app config, e.g. set base path, add hostsettings.json, env variables, etc
  • dependency injection, e.g. various hosted services
  • logging capabilities, e.g. console logging

The HostBuilder class is available from the following namespace, implementing the IHostBuilder interface:

using Microsoft.Extensions.Hosting;

At a minimum, the Main() method of your .NET Core app would look like the following:

public static async Task Main(string[] args)
{
   var host = new HostBuilder()
      .Build(); 

   await host.RunAsync();
}

Here, the Build() method initializes the host, so (as you may expect) it can only be called once for initialization. Additional options can be configured by calling the ConfigureServices() method before initializing the host with Build().

var host = new HostBuilder()
   .ConfigureServices((hostContext, services) =>
   {
      services.Configure<HostOptions>(option =>
      {
         // option.SomeProperty = ...
      });
   })
   .Build();

Here, the ConfigureServices() method takes in a HostBuilderContext and an injected collection of IServiceCollection services. The options set in the Configure() can be used to set additional HostOptions. Currently, HostOptions just has one property, i.e. ShutdownTimeout.

You can see more configuration capabilities in the official sample, broken down into the snippets below:

Host Config Snippet:

.ConfigureHostConfiguration(configHost =>
{
   configHost.SetBasePath(Directory.GetCurrentDirectory());
   configHost.AddJsonFile("hostsettings.json", optional: true);
   configHost.AddEnvironmentVariables(prefix: "PREFIX_");
   configHost.AddCommandLine(args);
})

App Config Snippet: 

.ConfigureAppConfiguration((hostContext, configApp) =>
{
   configApp.AddJsonFile("appsettings.json", optional: true);
   configApp.AddJsonFile(
      $"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", 
      optional: true);
   configApp.AddEnvironmentVariables(prefix: "PREFIX_");
   configApp.AddCommandLine(args);
})

Dependency Injection Snippet: 

.ConfigureServices((hostContext, services) =>
{
   services.AddHostedService<LifetimeEventsHostedService>();
   services.AddHostedService<TimedHostedService>();
})

Logging Snippet: 

.ConfigureLogging((hostContext, configLogging) =>
{
   configLogging.AddConsole();
   configLogging.AddDebug();
})

More History: Web Host Builder in 2.x

The WebHostBuilder class was made available from the following namespace (specific to ASP .NET Core), implementing the IWebHostBuilder interface:

using Microsoft.AspNetCore.Hosting;

The Web Host Builder in ASP .NET Core was used for hosting web apps in v2.x. As mentioned in the previous section, it has since been replaced by the Generic Host Builder as of v3.0. At a minimum, the Main() method of your ASP .NET Core 2.x web app would have looked like the following:

public class Program
{
   public static void Main(string[] args)
   {
      CreateWebHostBuilder(args).Build().Run();
   } 

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

If you’re not familiar with the shorthand syntax of the helper method CreateWebHostBuilder() shown above, here’s what it would normally look like, expanded:

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

NOTE: This type of C# syntax is known as an Expression Body Definition, introduced for methods in C# 6.0, and additional features in C# 7.0.

The CreateDefaultBuilder() method performs a lot of “magic” behind the scenes, by making use of pre-configured defaults. From the official documentation, here is a summary of the default configuration from the Default Builder:

For more information on some of the above, here are some other blog posts that you may find useful:

Generic Host Builder for Web Apps in 3.x

As of 2019, ASP .NET Core 3.x allows you to use the updated Generic Host Builder instead of the Web Host Builder in your web apps. The ASP .NET Core templates were updated to include the Generic Host Builder as of v3.0 Preview 2. You should use v3.1 since it’s a LTS (Long-Time Support) release.

At a minimum, the Main() method of your .NET Core 3.1 web app would now look like the following:

public static void Main(string[] args)
{
   CreateHostBuilder(args)
      .Build()
      .Run();
}

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

Here’s an expanded representation of the CreateHostBuilder() method:

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

This CreateHostBuilder() method in the 3.x template looks very similar to the 2.x call to CreateWebHostBuilder() mentioned in the previous section. In fact, the main difference is that the call to WebHost.CreateDefaultBuilder() is replaced by Host.CreateDefaultBuilder(). Using the CreateDefaultBuilder() helper method makes it very easy to switch from v2.x to v3.x.

Another difference is the call to ConfigureWebHostDefaults(). Since the new host builder is a Generic Host Builder, it makes sense that we have to let it know that we intend to configure the default settings for a Web Host. The ConfigureWebHostDefaults() method does just that.

Going forward, it’s important to know the following:

  • WebHostBuilder has now been deprecated and could be removed in the near future
  • However, the IWebHostBuilder interface will remain
  • You won’t be able to inject just any service into the Startup class…
  • … instead, you have IHostingEnvironment and IConfiguration

If you’re wondering about the reason for the limitation for injecting services, this change prevents you from injecting services into the Startup class  before ConfigureServices() gets called.

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

EF Core Relationships in ASP .NET Core 3.1

By Shahed C on February 3, 2020

This is the fifth 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:

E is for EF Core Relationships

In my 2018 series, we covered EF Core Migrations to explain how to add, remove and apply Entity Framework Core Migrations in an ASP .NET Core web application project. In this article, we’ll continue to look at the newer 2020 NetLearner project, to identify entities represented by C# model classes and the relationships between them.

NOTE: Please note that NetLearner is a work in progress as of this writing, so its code is subject to change. The UI web apps still needs work (and will be updated at a later date) but the current version has the following models with the relationships shown below:

NetLearner database diagram
NetLearner database diagram

Classes and Relationships

The heart of the application is the LearningResource class. This represents any online learning resource, such as a blog post, single video, podcast episode, ebook, etc that can be accessed with a unique URL.

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; }

    public ContentFeed ContentFeed { get; set; }

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

The ContentFeed class represents the RSS Feed (or channel information) for an online resource, a URL that can be used to retrieve more information about the online resource, if available.

public class ContentFeed
{
    public int Id { get; set; }

    [DisplayName("Feed URL")]
    public string FeedUrl { get; set; }

    public int LearningResourceId { get; set; }
    public LearningResource LearningResource { get; set; }
}

The ResourceList class represents a logical container for learning resources in the system. It is literally a list of items, where the items are your learning resources.

public class ResourceList
{
    public int Id { get; set; }

    public string Name { get; set; }

    public List<LearningResource> LearningResources { get; set; }
} 

The TopicTag class represents a single “tag” value that can be used to categorize online resources. Possibly values could be “.NET Core”, “ASP.NET Core” and so on.

public class TopicTag
{
    public int Id { get; set; }

    [DisplayName("Tag")]
    public string TagValue { get; set; }

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

At this point, you may have noticed both the LearningResource and TopicTag classes contain a List<T> property of LearningResourceTopicTag. If you browse the database diagram, you will notice that this table appears as a connection between the two aforementioned tables, to establish a many-to-many relationship. (more on this later)

The following diagram shows an example of how the a LearningResource (e.g. link to a doc/video) is a part of a ResourceList, while each LearningResource also has a link back to its root site, channel or RSS feed (via ContentFeed).

One to One

Having looked through the above entities and relationships, we can see that each LearningResource has a ContentFeed. This is an example of a 1-to-1 relationship. For example:

  • Learning Resource = Wake Up and Code! blog site
  • Content Feed = RSS Feed for blog site

In the two classes, we see the following code:

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; }

public ContentFeed ContentFeed { get; set; }

public List<LearningResourceTopicTag> LearningResourceTopicTags { get; set; }
}
public class ContentFeed
{
public int Id { get; set; }

[DisplayName("Feed URL")]
public string FeedUrl { get; set; }

public int LearningResourceId { get; set; }
public LearningResource LearningResource { get; set; }
}

Each Learning Resource has a corresponding Content Feed, so the LearningResource class has a property for ContentFeed. That’s pretty simple. But in the  ContentFeed class, you don’t necessarily need a property pointing back to the  LearningResource . In fact, all you need is a  LearningResourceId property. EF Core will ensure that LearningResource.Id points to ContentFeed.LearningResourceId in the database. But to help with object-property navigation in your code, it is useful to include an actual LearningResource object in the ContentFeed class to point back to LearningResource.

One to One Relationship
One to One Relationship

Another way of looking at the One-to-One relationship is to view the constraints of each database entity in the visuals below. Note that both tables have an Id field that is a Primary Key (inferred by EF Core) while the ContentFeeds table also has a Foreign Key for the LearningResourceId field used for the constraint in the relationship.

LearningResources table
LearningResources table
ContentFeeds table
ContentFeeds table
One to One Relationship
One to One Relationship

One to Many

Next, let’s take a look at the One-to-Many relationship for each ResourceList that has zero or more LearningResources. For example:

  • Resource List = ASP .NET Core Blogs (parent container)
  • Learning Resource = ASP .NET Core A-Z Blog Series (single URL)

In the two classes, we see the following code:

public class ResourceList
{
public int Id { get; set; }

public string Name { get; set; }

public List<LearningResource> LearningResources { get; set; }
}
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; }

public ContentFeed ContentFeed { get; set; }

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

Each Resource List has zero or more Learning Resources, so the ResourceList class has a List<T> property for LearningResources. This is even simpler than the previously described 1-to-1 relationship. In the LearningResource class, you don’t necessarily need a property pointing back to the ResourceList. But once again, to help with object-property navigation in your code, it is useful to include an actual ResourceList object in the LearningResource class to point back to ResourceList.

One to Many Relationship
One to Many Relationship

Another way of looking at the One-to-Many relationship is to view the constraints of each database entity in the visuals below. Note that both tables have an Id field that is a Primary Key (once again, inferred by EF Core) while the ResourceLists table also has a Foreign Key for the  ResourceListsId field used for the constraint in the relationship.

One to Many Constraint
One to Many Constraint

Many to Many

Finally, let’s also take a look at a Many-to-Many relationship, for each TopicTag and LearningResource, either of which can have many of the other. For example:

  • Topic Tag = “ASP .NET Core” (tag as a text description)
  • Learning Resource = Specific blog post on site (single URL)

This relationship is a little more complicated than all of the above, as we will need a “join table” to connect the two tables in question. Not only that, we will have to describe the entity in the C# code with connections to both tables we would like to connect with this relationship.

In the two classes we would like to connect, we see the following code:

public class TopicTag
{
    public int Id { get; set; }

    [DisplayName("Tag")]
    public string TagValue { get; set; }

    public List<LearningResourceTopicTag> LearningResourceTopicTags { get; set; }
} 
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; }

public ContentFeed ContentFeed { get; set; }

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

Next, we have the LearningResourceTopicTag class as a “join entity” to connect the above:

public class LearningResourceTopicTag
{
    public int LearningResourceId { get; set; }
    public LearningResource LearningResource { get; set; }

    public int TopicTagId { get; set; }
    public TopicTag TopicTag { get; set; }

}

This special class has the following properties:

  • LearningResourceId: integer value, pointing back to LearningResource.Id
  • LearningResource: optional “navigation” property, reference back to connected LearningResource entity
  • TopicTagId: integer value, pointing back to TopicTag.Id
  • TopicTag:  optional “navigation” property, reference back to connected TopicTag entity

To learn more about navigation properties, check out the official docs at:

Many to Many Relationship
Many to Many Relationship

Another way of looking at the Many-to-Many relationship is to view the constraints of each database entity in the visuals below. Note that the two connected tables both have an Id field that is a Primary Key (yes, inferred by EF Core!) while the LearningResourceTopicTag table has a Composite Key for the TopicTagId and LearningResourceId fields used for the constraints in the relationship.

Constraints for LearningResources
Constraints for LearningResources
 Constraints for TopicTags
Constraints for TopicTags

The composite key is described in the LibDbContext class inside the OnModelCreating() method:

public class LibDbContext : IdentityDbContext
{
    ...
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
       ...
       modelBuilder.Entity<LearningResourceTopicTag>()
        .HasKey(lrtt => new { lrtt.LearningResourceId, lrtt.TopicTagId });
    }
}

Here, the HasKey() method informs EF Core that the entity LearningResourceTopicTag has a composite key defined by both LearningResourceId and TopicTagId.

References

For more information, check out the list of references below.

For detailed tutorials that include both Razor Pages and MVC, check out the official tutorials below:

Deploying ASP .NET Core 3.1 to Azure App Service

By Shahed C on January 27, 2020

This is the fourth 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:

D is for Deploying to Azure App Service

In this article, we’ll explore several options for deploying an ASP .NET Core web app to Azure App Service in the cloud. From the infamous Right-Click-Publish to fully automated CI/CD, you’ll learn about the latest Deployment Center option in the Azure Portal for App Service for web apps.

NOTE: If you’re looking for information on deploying to Docker or Kubernetes, please check out the following docs instead:

Right-Click Publish (aka Friends Don’t Let Friends Right-Click Publish)

If you’ve made it this far, you may be thinking one of the following:
a. “Hey, this is how I deploy my web apps right now!”
or
b. “Hey wait a minute, I’ve heard that you should never do this!”

Well, there is a time and place for right-click publish. There have been many debates on this, so I won’t go into the details, but here are some resources for you to see what others are saying:

So, what’s a web developer to do? To quote from the aforementioned MSDN article, “Continuing with the theme of prototyping and experimenting, right click publish is the perfect way for existing Visual Studio customers to evaluate Azure App Service (PAAS). By following the right click publish flow you get the opportunity to provision new instances in Azure and publish your application to them without leaving Visual Studio:”

In other words, you can use this approach for a quick test or demo, as shown in the screenshots below for Visual Studio.

  1. Right-click your ASP .NET Core web app project in Solution Explorer and select Publish.
  2. Click the Start button on the screen that appears and follow the onscreen instructions.
  3. Ensure that you’re logged in to the correct Azure subscription account you want to publish to.
Right-click, Publish from Solution Explorer
Right-click, Publish from Solution Explorer
Pick a Publish Target
Pick a Publish Target

Web Apps in the Azure Portal

In the screenshot above, you may notice an option to “Import Profile” using the button on the lower left. This allows you to import a Web App profile file that was generated by exporting it from an existing Azure Web App. To grab this profile file, simply navigate to your existing Web App in the Azure Portal and click on “Get publish profile” in the top toolbar of your Web App, shown below:

Get Publish Profile
Get Publish Profile

If you want to create a new Web App in Azure starting with the Azure Portal, follow the instructions below:

  1. Log in to the Azure at https://portal.azure.com
  2. On the top left, click + Create a resource
  3. Select “Web App” or search for it if you don’t see it.
  4. Enter/select the necessary values:
    • Subscription (select a subscription)
    • Resource Group (create or use existing to group resources logically)
    • Web App name (enter a unique name)
    • Publish (Code or Docker Image)
    • Runtime stack (.NET Core)
    • App Service Plan (create or use existing to set location and pricing tier)
    • OS (Windows or Linux)
    • Region (e.g. East US)
  5. Click Next through the screens and then click the Create button to complete the creation of your new Web App.
Create New Web App
Create New Web App

Now you can deploy to this Web App using any method you choose.

Runtime Options

If you like to stay ahead of ASP .NET Core releases, you may be using a pre-release version of the runtime. As of this writing, the latest stable version of ASP .NET Core is version 3.1, which is already available on Azure. If you’re looking for future preview releases, Azure App Service also has an option to install an Extension for preview runtimes.

To find the proper runtime:

  1. Navigate to your Web App in the Azure Portal.
  2. Click on Extensions under Development Tools.
  3. Click + Add to add a new extension.
  4. Choose an extension to configure required settings.
  5. Accept the legal terms and complete the installation.
Add Extension
Add Extension

Your options may include both 32-bit (x86) and 64-bit (x64) versions of the ASP .NET Core runtime and any preview versions of future releases. When planning ahead for multiple environments, you also have the option to deploy to Deployments Slots. This feature is available in StandardPremium or Isolated App Service Plan tiers and will covered in a future blog post in this series.

If you’re interested in Deployment Slots right now, check out the official docs at:

Deployment Center

In the list of features for your Web App, you will find an option to open up the new Deployment Center. Note that this has replaced the old Deployment Options. Let’s go over each of these options:

  1. Azure Repos
  2. Github
  3. Bitbucket
  4. Local Git
  5. OneDrive
  6. Dropbox
  7. External
  8. FTP
Deployment Options in Deployment Center
Deployment Options in Deployment Center
More Deployment Options
More Deployment Options

If you choose Azure Repos, you can set up your web app’s CI (Continuous Integration) system with an Azure Repo, which is part of Microsoft’s Azure DevOps services (formerly known as VSTS, aka Visual Studio Team Services). You will have options for using App Service as a Kudu build server or Azure Pipelines as your CI build system.

 Azure Repos choices: Kudu or Pipelines?
Azure Repos choices: Kudu or Pipelines?

If you choose Github or BitBucket or even a local Git account, you’ll have the ability to authorize that account to publish a specific repo, every time a developer pushes their code.

 Authorize Github/Bitbucket
Authorize Github/Bitbucket

If you choose OneDrive or DropBox, you’ll have ability to authorize your App Service to pick up files deployed to a shared folder in either location.

 Authorize OneDrive/DropBox
Authorize OneDrive/DropBox

You may also select an External repo or FTP source. To learn more about Azure Repos and Azure Pipelines, check out the official docs:

GitHub Repos (includes FREE option!)

If you’ve been using GitHub for public open-source projects or private projects on paid accounts, now is a great to time to create private repositories for free! In 2019, GitHub started offering free unlimited private repos, limited to 3 collaborators. This new free option comes with issue/bug tracking and project management as well.

For more information on GitHub pricing, check out their official pricing page:

 GitHub pricing: Free vs Pro, Team and Enterprise
GitHub pricing: Free vs Pro, Team and Enterprise

Now you can easily set up your starter projects in a private GitHub repository and take advantage of the aforementioned CI/CD setup without having to choose between paying a GitHub fee or making all your repos public.

CLI Commands

If you wish to publish to Azure App service using CLI (Command Line Interface) Commands, you may use the following commands, where you can choose a name for your Web App, Resource Group, App Sevice Plan, etc. Single-letter flags are usually preceded by a single hyphen, while flags spelled out with completed words are usually preceded by two hyphens.

First, install the Azure CLI in case you don’t have it already:

Authenticate yourself:

> az login

Create a new resource group:

> az group create -l <LOCATION> -n <RSG>
> az group create --location <LOCATION> --name <RSG>

Create a new App Service Plan, where <SKUCODE> sku may be F1 or FREE, etc

> az appservice plan create -g <RSG> -n <ASP> --sku <SKUCODE> 
> az appservice plan create --resource-group <RSG> --name <ASP> --sku <SKUCODE>

From the documentation, the SKU Codes include: F1(Free), D1(Shared), B1(Basic Small), B2(Basic Medium), B3(Basic Large), S1(Standard Small), P1V2(Premium V2 Small), PC2 (Premium Container Small), PC3 (Premium Container Medium), PC4 (Premium Container Large), I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large).

Create a new Web App within a Resource Group, attached to an App Service Plan:

> az webapp create -g <RSG> -p <ASP> -n <APP> 
> az webapp create --resource-group <RSG> --plan <ASP> --name <APP>

The above command should create a web app available at the following URL:

  • http://<APP>.azurewebsites.net

To push your commits to a Git Repo and configure for App Service Deployment, use the following CLI commands:

Create a new Git repo or reinitialize an existing one:

> git init

Add existing files to the index:

> git add .

Commit your changes with a commit message:

> git commit -m "<COMMIT MESSAGE>“

Set your FTP credentials and Git deployment credentials for your Web App:

> az webapp deployment user set --user-name <USER>

Configure an endpoint and add it as a git remote.

> az webapp deployment source config-local-git -g <RSG> -n <APP> --out tsv  
> az webapp deployment source config-local-git --resource-group <RSG> --name <APP> --out tsv
> git remote add azure <GIT URL>

The value for GIT URL is the value of the Git remote, e.g.

  • https://<USER>@<APP>.scm.azurewebsites.net/<APP>.git

Finally, push to the Azure remote to deploy your Web App:

> git push azure master

For more information on CLI Commands for Git and Azure App Service, check out the official docs:

Azure DevOps and YAML

Wait, what about Azure DevOps and YAML and Pipelines?

Since this is an A-Z series, you will have to wait for “Y is for YAML” to get more detailed information on constructing your build pipeline using YAML in Azure DevOps. If you can’t wait that long, feel free to check out the following .yml sample I uploaded for use with an ASP .NET Core 3.1:

If you’re reading this after June 2020, simply jump right to the “Y is for YAML” post in the 2020 A-Z series.

BONUS: for Azure SQL Database Deployment, watch the following video on MSDN Channel 9:

References