Dr. Peter Trimmel
Published © MIT

Home Control Web

ASP.NET Web application to integrate various devices such as a home control system, ventilation unit, power meter, PV, heating boiler...

IntermediateProtip3,367
Home Control Web

Things used in this project

Story

Read more

Schematics

Overview

Picture2 lcbzv0d52v

Code

Program.cs

C#
The main application entry for the ASP.NET web application running the web host.
namespace HomeControlWeb
{
    using System;
    using System.IO;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;

    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
    using Microsoft.Extensions.Configuration;
    using Serilog;

    using HomeControlWeb.Models;

    /// <summary>
    /// Main application class providing the main entry point <see cref="Main(string[])"/>.
    /// </summary>
    public static class Program
    {
        /// <summary>
        /// This is the main entry point to the application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Starting web host...");
                var host = BuildWebHost(args);
                Log.Information("Starting web host");
                host.Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
                Console.WriteLine($"Web host terminated with exception: {ex.Message}.");
            }
            finally
            {
                Log.Information("Web host terminated");
                Log.CloseAndFlush();
                Console.WriteLine("Web host terminated.");
            }
        }

        /// <summary>
        /// Creates a host using an instance of WebHostBuilder using the <see cref="Startup"/> class.
        /// Note that using HTTPS is configured for the kestrel web server and a Serilog file logger
        /// is configured using rolling files in the Log subdirectory.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        /// <returns>The web host.</returns>
        public static IWebHost BuildWebHost(string[] args)
        {
            // Getting configuration and app settings.
            var config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables()
                .AddCommandLine(args)
                .Build();

            AppSettings settings = new AppSettings();
            config.GetSection("AppSettings").Bind(settings);

            // Configure logger.
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(config)
                .Enrich.FromLogContext()
                .WriteTo.Debug()
                .CreateLogger();

            // Setup web host.
            return WebHost.CreateDefaultBuilder(args)
                .UseConfiguration(config)
                .UseSerilog()
                .UseKestrel(options =>
                {
                    var listen = settings.Listen;

                    // Run callbacks on the transport thread
                    options.ApplicationSchedulingMode = SchedulingMode.Inline;

                    if (string.IsNullOrEmpty(listen.Certificate))
                    {
                        // Setup HTTP
                        options.Listen(IPAddress.Any, listen.BasePort);
                    }
                    else
                    {
                        var certificate = new X509Certificate2(listen.Certificate, listen.Password, X509KeyStorageFlags.MachineKeySet);

                        // Setup HTTPS
                        options.Listen(IPAddress.Any, listen.BasePort, listenOptions =>
                        {
                            listenOptions.NoDelay = false;
                            listenOptions.UseHttps(certificate);
                        });
                    }
                })
                .UseStartup<Startup>()
                .Build();
        }
    }
}

Startup.cs

C#
Configuration of the ASP.NET application and services.
namespace HomeControlWeb
{
    using System;
    using System.IO;
    using System.Net;

    using Microsoft.ApplicationInsights.Extensibility;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc.Infrastructure;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Routing;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.PlatformAbstractions;

    using HomeControlWeb.Data;
    using HomeControlWeb.Models;
    using HomeControlWeb.Services;
    using HomeControlWeb.Swagger;
    using HomeControlWeb.Extensions;
    using HomeControlWeb.Hubs;

    /// <summary>
    /// The Startup class configures services and the application's request pipeline.
    /// </summary>
    public class Startup
    {
        /// <summary>
        /// The Startup class constructor accepts dependencies that are provided through dependency injection.
        /// </summary>
        /// <param name="configuration">The configuration instance.</param>
        public Startup(IConfiguration configuration)
        {
            ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
            Configuration = configuration;
        }

        /// <summary>
        /// The configuration property.
        /// </summary>
        public IConfiguration Configuration { get; }

        /// <summary>
        /// This method gets called by the runtime. Use this method to add services to the container.
        /// It is called before the Configure method by the web host.
        /// </summary>
        /// <param name="services">The service collection.</param>
        public void ConfigureServices(IServiceCollection services)
        {
            // Get application settings.
            AppSettings settings = new AppSettings();
            Configuration.GetSection("AppSettings").Bind(settings);

            // Add SQLite database for identity service.
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

            // Configure identity (user, password, validation).
            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.Configure<IdentityOptions>(options =>
            {
                // Password settings
                options.Password.RequireDigit = true;
                options.Password.RequiredLength = 8;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = true;
                options.Password.RequireLowercase = false;
                options.Password.RequiredUniqueChars = 6;

                // Lockout settings
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
                options.Lockout.MaxFailedAccessAttempts = 10;
                options.Lockout.AllowedForNewUsers = true;

                // User settings
                options.User.RequireUniqueEmail = true;
            });

            services.ConfigureApplicationCookie(options =>
            {
                // Cookie settings
                options.Cookie.HttpOnly = true;
                options.Cookie.Expiration = TimeSpan.FromDays(150);
                options.SlidingExpiration = true;
            });

            // Includes support for Razor Pages and controllers.
            services.AddMvc()
                .AddRazorPagesOptions(options =>
                {
                    options.Conventions.AuthorizeFolder("/Account/Manage");
                    options.Conventions.AuthorizePage("/Account/Logout");
                })
                .AddJsonOptions(options =>
                {
                    options.SerializerSettings.Formatting = Formatting.Indented;
                });

            // Required for handling of AntiForgeryToken.
            services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");

            // Required to use the Options<T> pattern for application settings.
            services.AddOptions();
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

            // Add application services for sending emails.
            services.AddSingleton<IEmailSender, EmailSender>();
            services.Configure<SenderOptions>(Configuration);

            // Required to use paging for large datasets.
            services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
            services.AddScoped<IUrlHelper>(factory =>
            {
                var actionContext = factory.GetService<IActionContextAccessor>().ActionContext;
                return new UrlHelper(actionContext);
            });

            // Adding SignalR support.
            services.AddSignalR();

            // Adding additional services.
            services.AddSingleton<PAC3200Provider>();
            services.AddSingleton<KWLEC200Provider>();
            services.AddSingleton<ETAPU11Provider>();
            services.AddSingleton<FroniusProvider>();
            services.AddSingleton<NetatmoProvider>();
            services.AddSingleton<ZipatoProvider>();
            services.AddSingleton<OverviewProvider>();
            services.AddSingleton<ThingSpeakProvider>();
            services.AddSingleton<IHostedService, PAC3200Monitor>();
            services.AddSingleton<IHostedService, KWLEC200Monitor>();
            services.AddSingleton<IHostedService, ETAPU11Monitor>();
            services.AddSingleton<IHostedService, FroniusMonitor>();
            services.AddSingleton<IHostedService, NetatmoMonitor>();
            services.AddSingleton<IHostedService, ZipatoMonitor>();
            services.AddSingleton<IHostedService, ThingSpeakMonitor>();

            // Add Swagger support using the application settings.
            services.AddSwaggerGen(c =>
            {
                var basePath = PlatformServices.Default.Application.ApplicationBasePath;
                var xmlPath = Path.Combine(basePath, "HomeControlWeb.xml");
                var swagger = settings.Swagger;

                c.SwaggerDoc(swagger.Overview.Version, swagger.Overview);
                c.SwaggerDoc(swagger.ETAPU11.Version, swagger.ETAPU11);
                c.SwaggerDoc(swagger.PAC3200.Version, swagger.PAC3200);
                c.SwaggerDoc(swagger.KWLEC200.Version, swagger.KWLEC200);
                c.SwaggerDoc(swagger.Fronius.Version, swagger.Fronius);
                c.SwaggerDoc(swagger.Netatmo.Version, swagger.Netatmo);
                c.SwaggerDoc(swagger.Zipato.Version, swagger.Zipato);

                c.IncludeXmlComments(xmlPath);
                c.DescribeAllEnumsAsStrings();
                c.DescribeStringEnumsInCamelCase();
                c.OperationFilter<DataAnnotationAttributesOperationFilter>();
            });
        }

        /// <summary>
        /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        /// The request pipeline consists of a sequence of request delegates, called one after the other.
        /// </summary>
        /// <param name="app">The IApplicationBuilder instance.</param>
        /// <param name="env">The IHostingEnvironment instance.</param>
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // Get application settings.
            AppSettings settings = new AppSettings();
            Configuration.GetSection("AppSettings").Bind(settings);

            // Setup error handling.
            if (env.IsDevelopment())
            {
                TelemetryConfiguration.Active.DisableTelemetry = true;
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            // Use static files and authentication.
            app.UseStaticFiles();
            app.UseAuthentication();

            // Use default routing to controller actions.
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{action=Index}/{id?}");
            });

            // Use SignalR and setup the various hubs.
            app.UseSignalR(router =>
            {
                router.MapHub<OverviewHub>("OverviewHub");
                router.MapHub<ETAPU11Hub>("ETAPU11Hub");
                router.MapHub<PAC3200Hub>("PAC3200Hub");
                router.MapHub<KWLEC200Hub>("KWLEC200Hub");
                router.MapHub<FroniusHub>("FroniusHub");
                router.MapHub<NetatmoHub>("NetatmoHub");
                router.MapHub<ZipatoHub>("ZipatoHub");
            });

            // Use Swagger and setup Swagger UI.
            app.UseSwaggerAuthorized();
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                var swagger = settings.Swagger;

                c.SwaggerEndpoint($"/swagger/{swagger.Overview.Version}/swagger.json", $"{swagger.Overview.Title} - {swagger.Overview.Version}");
                c.SwaggerEndpoint($"/swagger/{swagger.ETAPU11.Version}/swagger.json", $"{swagger.ETAPU11.Title} - {swagger.ETAPU11.Version}");
                c.SwaggerEndpoint($"/swagger/{swagger.PAC3200.Version}/swagger.json", $"{swagger.PAC3200.Title} - {swagger.PAC3200.Version}");
                c.SwaggerEndpoint($"/swagger/{swagger.KWLEC200.Version}/swagger.json", $"{swagger.KWLEC200.Title} - {swagger.KWLEC200.Version}");
                c.SwaggerEndpoint($"/swagger/{swagger.Fronius.Version}/swagger.json", $"{swagger.Fronius.Title} - {swagger.Fronius.Version}");
                c.SwaggerEndpoint($"/swagger/{swagger.Netatmo.Version}/swagger.json", $"{swagger.Netatmo.Title} - {swagger.Netatmo.Version}");
                c.SwaggerEndpoint($"/swagger/{swagger.Zipato.Version}/swagger.json", $"{swagger.Zipato.Title} - {swagger.Zipato.Version}");

                c.DisplayRequestDuration();
                c.DocExpansion(DocExpansion.List);
                c.EnableFilter();
                c.DefaultModelRendering(ModelRendering.Example);

                c.ValidatorUrl(null);
                c.SupportedSubmitMethods(SubmitMethod.Get, SubmitMethod.Put, SubmitMethod.Post);
            });
        }
    }
}

HomeControlWeb.csproj

XML
Example for the project file showing the settings and dependencies.
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <RuntimeIdentifiers>win-arm;win-x64</RuntimeIdentifiers>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>homecontrolweb.xml</DocumentationFile>
    <NoWarn>1701;1702;1705;1591</NoWarn>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DocumentationFile>homecontrolweb.xml</DocumentationFile>
    <NoWarn>1701;1702;1705;1591</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <None Update="HomeControlWeb.db" CopyToOutputDirectory="PreserveNewest" />
    <None Update="HomeControlWeb.pfx" CopyToOutputDirectory="PreserveNewest" />
    <None Update="homecontrolweb.xml" CopyToOutputDirectory="PreserveNewest" />
    <None Remove="Properties\PublishProfiles\FolderProfile.pubxml" />
    <None Remove="Properties\PublishProfiles\FolderProfile1.pubxml" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="ETAPU11Lib" Version="1.0.5" />
    <PackageReference Include="FroniusLib" Version="2.0.4" />
    <PackageReference Include="KWLEC200Lib" Version="2.0.6" />
    <PackageReference Include="MailKit" Version="2.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha2-final" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.0.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.1" PrivateAssets="All" />
    <PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" PrivateAssets="All" />
    <PackageReference Include="NetatmoLib" Version="2.0.7" />
    <PackageReference Include="PAC3200Lib" Version="2.0.7" />
    <PackageReference Include="Serilog.AspNetCore" Version="2.1.0" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="2.5.0" />
    <PackageReference Include="Serilog.Sinks.ColoredConsole" Version="3.0.1" />
    <PackageReference Include="Serilog.Sinks.Debug" Version="1.0.0" />
    <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="2.0.0" />
    <PackageReference Include="Syncfusion.Compression.NETStandard20" Version="15.4200.0.20" />
    <PackageReference Include="Syncfusion.EJ.Pivot" Version="15.4600.0.20" />
    <PackageReference Include="Syncfusion.EJ.AspNet.Core" Version="15.4600.0.20" />
    <PackageReference Include="ThingSpeakLib" Version="2.0.4" />
    <PackageReference Include="ZipatoLib" Version="2.0.8" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
  </ItemGroup>
  <ItemGroup>
    <Folder Include="Logs\" />
  </ItemGroup>
  <ItemGroup>
    <Content Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</Project>

Credits

Dr. Peter Trimmel

Dr. Peter Trimmel

2 projects • 5 followers
Programming and playing with computers for more than 40 years....

Comments