mirror of
https://github.com/Sarsoo/Spotify.NET.git
synced 2024-12-24 15:06:26 +00:00
Refactor ASP.NET Example to use AspNet.Security.OAuth.Spotify
This commit is contained in:
parent
9a6bd1b456
commit
58de9d9a4e
@ -1,15 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP
|
||||
{
|
||||
public static class ClaimsExtensions
|
||||
{
|
||||
public static string GetSpecificClaim(this ClaimsIdentity claimsIdentity, string claimType)
|
||||
{
|
||||
Claim claim = claimsIdentity.Claims.FirstOrDefault(x => x.Type == claimType);
|
||||
|
||||
return claim != null ? claim.Value : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SpotifyAPI.Web.Auth;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
using SpotifyAPI.Web.Examples.ASP.Models;
|
||||
using SpotifyAPI.Web.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP.Controllers
|
||||
{
|
||||
public class Auth : Controller
|
||||
{
|
||||
public IConfiguration Configuration { get; set; }
|
||||
|
||||
public Auth(IConfiguration config)
|
||||
{
|
||||
Configuration = config;
|
||||
}
|
||||
|
||||
private static Dictionary<string, AuthorizationCodeAuth> LoginRequests { get; } = new Dictionary<string, AuthorizationCodeAuth>();
|
||||
|
||||
// GET
|
||||
public IActionResult Login()
|
||||
{
|
||||
string guid = Guid.NewGuid().ToString();
|
||||
var auth = new AuthorizationCodeAuth( // TODO: Extract to own method
|
||||
Configuration["spotify_client_id"], Configuration["spotify_client_secret"],
|
||||
"http://localhost:5000/auth/callback", "", Scope.PlaylistReadPrivate | Scope.PlaylistReadPrivate, guid);
|
||||
|
||||
LoginRequests.Add(guid, auth); // TODO: Clean up after a timeout, else: DOS possible
|
||||
|
||||
return Redirect(auth.GetUri());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync();
|
||||
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Callback([FromQuery(Name = "code")] string code, [FromQuery(Name = "state")] string state,
|
||||
[FromQuery(Name = "error")] string error)
|
||||
{
|
||||
if (error != null)
|
||||
return View("Error", new ErrorViewModel { Message = error, RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
|
||||
|
||||
if(!LoginRequests.ContainsKey(state))
|
||||
return View("Error", new ErrorViewModel { Message = "Unknown login request", RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
|
||||
|
||||
AuthorizationCodeAuth auth = LoginRequests[state];
|
||||
Token token = await auth.ExchangeCode(code);
|
||||
|
||||
if (token.HasError())
|
||||
{
|
||||
return View("Error",
|
||||
new ErrorViewModel
|
||||
{
|
||||
Message = $"Unable to exchange Code: ${token.Error}",
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
|
||||
});
|
||||
}
|
||||
|
||||
var api = new SpotifyWebAPI
|
||||
{
|
||||
AccessToken = token.AccessToken,
|
||||
TokenType = token.TokenType
|
||||
};
|
||||
PrivateProfile profile = await api.GetPrivateProfileAsync();
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
// TODO: Extract claim types to either Enum or class
|
||||
new Claim("user_id", profile.Id),
|
||||
new Claim("display_name", profile.DisplayName ?? profile.Id),
|
||||
new Claim("access_token", token.AccessToken),
|
||||
new Claim("token_type", token.TokenType),
|
||||
new Claim("refresh_token", token.RefreshToken),
|
||||
new Claim("expires_in", token.ExpiresIn.ToString(CultureInfo.InvariantCulture))
|
||||
};
|
||||
await HttpContext.SignInAsync(new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user_id", "")));
|
||||
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SpotifyAPI.Web.Auth;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SpotifyAPI.Web.Examples.ASP.Models;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class HomeController : Controller
|
||||
{
|
||||
public async Task<ViewResult> Index()
|
||||
{
|
||||
var claimsIdent = User.Identity as ClaimsIdentity;
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
SpotifyWebAPI api = GetSpotifyApiFromUser(claimsIdent);
|
||||
if (api == null)
|
||||
return View(new HomeModel());
|
||||
|
||||
string userId = claimsIdent.GetSpecificClaim("user_id");
|
||||
var playlists = await api.GetUserPlaylistsAsync(userId);
|
||||
return View(new HomeModel
|
||||
public HomeController(ILogger<HomeController> logger)
|
||||
{
|
||||
Playlists = playlists
|
||||
});
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
if(!User.Identity.IsAuthenticated)
|
||||
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, "Spotify");
|
||||
|
||||
var accessToken = await HttpContext.GetTokenAsync("Spotify", "access_token");
|
||||
SpotifyWebAPI api = new SpotifyWebAPI
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TokenType = "Bearer"
|
||||
};
|
||||
|
||||
var savedTracks = await api.GetSavedTracksAsync(50);
|
||||
|
||||
return View(new IndexModel { SavedTracks = savedTracks });
|
||||
}
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
@ -37,18 +44,5 @@ namespace SpotifyAPI.Web.Examples.ASP.Controllers
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
|
||||
private static SpotifyWebAPI GetSpotifyApiFromUser(ClaimsIdentity claimsIdent)
|
||||
{
|
||||
// TODO: Add expires_in logic
|
||||
|
||||
string accessToken = claimsIdent.GetSpecificClaim("access_token");
|
||||
string tokenType = claimsIdent.GetSpecificClaim("token_type");
|
||||
return new SpotifyWebAPI
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TokenType = tokenType
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,5 @@ namespace SpotifyAPI.Web.Examples.ASP.Models
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP.Models
|
||||
{
|
||||
public class HomeModel
|
||||
{
|
||||
public Paging<SimplePlaylist> Playlists;
|
||||
}
|
||||
}
|
10
SpotifyAPI.Web.Examples.ASP/Models/IndexModel.cs
Normal file
10
SpotifyAPI.Web.Examples.ASP/Models/IndexModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using SpotifyAPI.Web.Models;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP.Models
|
||||
{
|
||||
public class IndexModel
|
||||
{
|
||||
public Paging<SavedTrack> SavedTracks;
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP
|
||||
@ -14,11 +13,14 @@ namespace SpotifyAPI.Web.Examples.ASP
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:7731",
|
||||
"sslPort": 44310
|
||||
"applicationUrl": "http://localhost:55802",
|
||||
"sslPort": 44320
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
@ -18,7 +18,7 @@
|
||||
"SpotifyAPI.Web.Examples.ASP": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<UserSecretsId>52b27bb1-dc8f-4151-8ad6-b6efdb8edbb7</UserSecretsId>
|
||||
<UserSecretsId>201e6943-a5b3-473c-b1da-a579b36ca283</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SpotifyAPI.Web.Auth\SpotifyAPI.Web.Auth.csproj" />
|
||||
<ProjectReference Include="..\SpotifyAPI.Web\SpotifyAPI.Web.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNet.Security.OAuth.Spotify" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,23 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using EnvironmentName = Microsoft.AspNetCore.Hosting.EnvironmentName;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private const string CookieScheme = "YourSchemeName";
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
@ -28,25 +20,24 @@ namespace SpotifyAPI.Web.Examples.ASP
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.Configure<CookiePolicyOptions>(options =>
|
||||
services.AddControllersWithViews();
|
||||
|
||||
services.AddAuthentication(o => o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
|
||||
options.CheckConsentNeeded = context => true;
|
||||
options.MinimumSameSitePolicy = SameSiteMode.None;
|
||||
});
|
||||
|
||||
services
|
||||
.AddAuthentication(CookieScheme) // Sets the default scheme to cookies
|
||||
.AddCookie(CookieScheme, options =>
|
||||
options.LoginPath = "/signin";
|
||||
options.LogoutPath = "/signout";
|
||||
})
|
||||
.AddSpotify(options =>
|
||||
{
|
||||
options.LoginPath = "/auth/login";
|
||||
var scopes = Scope.UserLibraryRead;
|
||||
options.Scope.Add(scopes.GetStringAttribute(","));
|
||||
|
||||
options.SaveTokens = true;
|
||||
options.ClientId = Configuration["client_id"];
|
||||
options.ClientSecret = Configuration["client_secret"];
|
||||
options.CallbackPath = "/callback";
|
||||
});
|
||||
|
||||
|
||||
|
||||
services
|
||||
.AddMvc(options => options.EnableEndpointRouting = false)
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
@ -58,23 +49,23 @@ namespace SpotifyAPI.Web.Examples.ASP
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseHttpsRedirection();
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
|
||||
|
||||
// app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseCookiePolicy();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseMvc(routes =>
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
endpoints.MapControllerRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,15 @@
|
||||
@model HomeModel
|
||||
@using System.Security.Claims
|
||||
@using Microsoft.AspNetCore.Authentication
|
||||
@model IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Home Page";
|
||||
}
|
||||
|
||||
<h2>Some Playlists (first 20)</h2>
|
||||
<div class="text-center">
|
||||
You have @Model.SavedTracks.Total saved tracks in your library! Here are 50 of them:
|
||||
|
||||
@if (Model.Playlists != null && !Model.Playlists.HasError())
|
||||
<ul>
|
||||
@foreach (var item in Model.SavedTracks.Items)
|
||||
{
|
||||
<dl>
|
||||
@foreach (var playlist in Model.Playlists.Items)
|
||||
{
|
||||
<dt>@playlist.Name</dt>
|
||||
<dd>@(playlist.Owner.DisplayName ?? playlist.Owner.Id)</dd>
|
||||
<li>@item.Track.Name</li>
|
||||
}
|
||||
</dl>
|
||||
}
|
||||
|
||||
<h2>HttpContext.User.Claims</h2>
|
||||
|
||||
<dl>
|
||||
@foreach (Claim claim in User.Claims)
|
||||
{
|
||||
<dt>@claim.Type</dt>
|
||||
<dd>@claim.Value</dd>
|
||||
}
|
||||
</dl>
|
||||
|
||||
<h2>AuthenticationProperties</h2>
|
||||
|
||||
<dl>
|
||||
@foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
|
||||
{
|
||||
<dt>@prop.Key</dt>
|
||||
<dd>@prop.Value</dd>
|
||||
}
|
||||
</dl>
|
||||
</ul>
|
||||
</div>
|
||||
|
6
SpotifyAPI.Web.Examples.ASP/Views/Home/Privacy.cshtml
Normal file
6
SpotifyAPI.Web.Examples.ASP/Views/Home/Privacy.cshtml
Normal file
@ -0,0 +1,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
@ -9,7 +9,6 @@
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Message:</strong> <code>@Model.Message</code>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
@ -1,20 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - SpotifyAPI.Web.Examples.ASP</title>
|
||||
|
||||
<environment include="Development">
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/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="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"/>
|
||||
</environment>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
</head>
|
||||
<body>
|
||||
@ -31,14 +21,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
|
||||
</li>
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<li>
|
||||
<form class="form-inline" asp-controller="Auth" asp-action="Logout" method="post" >
|
||||
<button type="submit" class="nav-link btn btn-link">Logout</button>
|
||||
</form>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,28 +36,13 @@
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© 2019 - SpotifyAPI.Web.Examples.ASP - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<environment include="Development">
|
||||
<script src="~/lib/jquery/dist/jquery.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
|
||||
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
|
||||
asp-fallback-test="window.jQuery"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
|
||||
</script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"
|
||||
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-xrRywqdh3PHs8keKZN+8zzc5TX0GRTLCcmivcbNJWm2rs5C8PRhcEn3czEjhAO9o">
|
||||
</script>
|
||||
</environment>
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
|
||||
@RenderSection("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,18 +1,2 @@
|
||||
<environment include="Development">
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-F6h55Qw6sweK+t7SiOJX+2bpSAa3b/fnlrVCJvmEj1A=">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-9GycpJnliUjJDVDqP0UEu/bsm9U+3dnQUH8+3W10vkY=">
|
||||
</script>
|
||||
</environment>
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
|
@ -1,7 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
|
@ -7,6 +7,23 @@ a.navbar-brand {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Provide sufficient contrast against white background */
|
||||
a {
|
||||
color: #0366d6;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
/* Sticky footer styles
|
||||
-------------------------------------------------- */
|
||||
html {
|
||||
@ -50,7 +67,5 @@ body {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
/* Set the fixed height of the footer here */
|
||||
height: 60px;
|
||||
line-height: 60px; /* Vertically center the text there */
|
||||
}
|
||||
|
@ -138,14 +138,14 @@ small {
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
@ -305,7 +305,6 @@ fieldset {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
|
Loading…
Reference in New Issue
Block a user