Add project files.
This commit is contained in:
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
363
.gitignore
vendored
Normal file
363
.gitignore
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
12
OfflineDemo/App.razor
Normal file
12
OfflineDemo/App.razor
Normal file
@ -0,0 +1,12 @@
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
12
OfflineDemo/Layout/MainLayout.razor
Normal file
12
OfflineDemo/Layout/MainLayout.razor
Normal file
@ -0,0 +1,12 @@
|
||||
@inherits LayoutComponentBase
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu />
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
78
OfflineDemo/Layout/MainLayout.razor.css
Normal file
78
OfflineDemo/Layout/MainLayout.razor.css
Normal file
@ -0,0 +1,78 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: rgb(1,26,113);
|
||||
background: linear-gradient(180deg, rgba(1,26,113,1) 0%, rgba(17,48,157,1) 100%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
44
OfflineDemo/Layout/NavMenu.razor
Normal file
44
OfflineDemo/Layout/NavMenu.razor
Normal file
@ -0,0 +1,44 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">OfflineDemo</a>
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door mx-2" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="todo">
|
||||
<span class="bi bi-list-task mx-2" aria-hidden="true"></span> ToDo List
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="shorts">
|
||||
<i class="bi bi-card-checklist mx-2" aria-hidden="true"></i> Shorts
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="upload">
|
||||
<span class="bi bi-cloud-arrow-up mx-2" aria-hidden="true"></span> Upload
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
}
|
||||
56
OfflineDemo/Layout/NavMenu.razor.css
Normal file
56
OfflineDemo/Layout/NavMenu.razor.css
Normal file
@ -0,0 +1,56 @@
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
color: #d7d7d7;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
12
OfflineDemo/Models/ShortsModel.cs
Normal file
12
OfflineDemo/Models/ShortsModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace OfflineDemo.Models;
|
||||
|
||||
public class ShortsModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Hashtags { get; set; }
|
||||
public string Mp4FileUrl { get; set; }
|
||||
public string ImageFileUrl { get; set; }
|
||||
public bool IsUploaded { get; set; } = false;
|
||||
}
|
||||
8
OfflineDemo/Models/TodoModel.cs
Normal file
8
OfflineDemo/Models/TodoModel.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace OfflineDemo.Models;
|
||||
|
||||
public class TodoModel
|
||||
{
|
||||
public DateTime CreationDate { get; set; } = DateTime.Now;
|
||||
public string ToDoItem { get; set; }
|
||||
public bool IsComplete { get; set; }
|
||||
}
|
||||
17
OfflineDemo/Models/UploadModel.cs
Normal file
17
OfflineDemo/Models/UploadModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace OfflineDemo.Models;
|
||||
|
||||
public class UploadModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "Title must be less than 100 characters.")]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[Required]
|
||||
[StringLength(500, ErrorMessage = "Description must be less than 500 characters.")]
|
||||
public string Description { get; set; } = "";
|
||||
|
||||
[StringLength(200, ErrorMessage = "Hashtags must be less than 200 characters.")]
|
||||
public string Hashtags { get; set; } = "#";
|
||||
}
|
||||
21
OfflineDemo/OfflineDemo.csproj
Normal file
21
OfflineDemo/OfflineDemo.csproj
Normal file
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
7
OfflineDemo/Pages/Home.razor
Normal file
7
OfflineDemo/Pages/Home.razor
Normal file
@ -0,0 +1,7 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
62
OfflineDemo/Pages/TikTokList.razor
Normal file
62
OfflineDemo/Pages/TikTokList.razor
Normal file
@ -0,0 +1,62 @@
|
||||
@page "/shorts"
|
||||
|
||||
@inject HttpClient httpClient
|
||||
@inject IConfiguration config
|
||||
|
||||
<h3>Shorts List</h3>
|
||||
|
||||
@if (shorts == null)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
else if (!shorts.Any())
|
||||
{
|
||||
<p>No shorts available.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>Hashtags</th>
|
||||
<th>Mp4 URL</th>
|
||||
<th>Image URL</th>
|
||||
<th>Uploaded</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var shortItem in shorts)
|
||||
{
|
||||
<tr>
|
||||
<td>@shortItem.Id</td>
|
||||
<td>@shortItem.Title</td>
|
||||
<td>@shortItem.Description</td>
|
||||
<td>@shortItem.Hashtags</td>
|
||||
<td><a href="@shortItem.Mp4FileUrl" target="_blank">View Video</a></td>
|
||||
<td><a href="@shortItem.ImageFileUrl" target="_blank">View Image</a></td>
|
||||
<td>@(shortItem.IsUploaded ? "Yes" : "No")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<ShortsModel>? shorts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"{config["ApiUrl"]}/shorts";
|
||||
shorts = await httpClient.GetFromJsonAsync<List<ShortsModel>>(url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Error fetching shorts: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
138
OfflineDemo/Pages/TikTokUpload.razor
Normal file
138
OfflineDemo/Pages/TikTokUpload.razor
Normal file
@ -0,0 +1,138 @@
|
||||
@page "/upload"
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inject HttpClient http
|
||||
@inject IConfiguration config
|
||||
@inject NavigationManager nav
|
||||
|
||||
<h3>Upload TikTok Video</h3>
|
||||
|
||||
@if (isSubmitting)
|
||||
{
|
||||
<p>Uploading...</p>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(resultMessage))
|
||||
{
|
||||
<p>@resultMessage</p>
|
||||
}
|
||||
|
||||
<EditForm Model="@uploadModel" OnValidSubmit="@HandleValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="title">Title</label>
|
||||
<InputText id="title" class="form-control" @bind-Value="uploadModel.Title" />
|
||||
<ValidationMessage For="@(() => uploadModel.Title)" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="description">Description</label>
|
||||
<InputTextArea id="description" class="form-control" @bind-Value="uploadModel.Description" />
|
||||
<ValidationMessage For="@(() => uploadModel.Description)" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="hashtags">Hashtags</label>
|
||||
<InputText id="hashtags" @oninput="HandleHashtagInput" class="form-control" @bind-Value="uploadModel.Hashtags" />
|
||||
<ValidationMessage For="@(() => uploadModel.Hashtags)" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="mp4Upload">MP4 File</label>
|
||||
<InputFile id="mp4Upload" class="form-control" OnChange="@HandleMp4FileChange" accept=".mp4,.mov" />
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="imageUpload">Image File (optional)</label>
|
||||
<InputFile id="imageUpload" class="form-control" OnChange="@HandleImageFileChange" accept=".jpg,.jpeg,.png" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
private UploadModel uploadModel = new();
|
||||
private IBrowserFile? mp4File;
|
||||
private IBrowserFile? imageFile;
|
||||
private bool isSubmitting = false;
|
||||
private string? resultMessage;
|
||||
|
||||
private void HandleMp4FileChange(InputFileChangeEventArgs e)
|
||||
{
|
||||
mp4File = e.File;
|
||||
}
|
||||
|
||||
private void HandleImageFileChange(InputFileChangeEventArgs e)
|
||||
{
|
||||
imageFile = e.File;
|
||||
}
|
||||
|
||||
private async Task HandleHashtagInput(ChangeEventArgs e)
|
||||
{
|
||||
var input = e.Value?.ToString() ?? "";
|
||||
if (input.EndsWith(" "))
|
||||
{
|
||||
// Add a hashtag before the space
|
||||
uploadModel.Hashtags = input.TrimEnd() + " #";
|
||||
// Optionally, reset cursor position
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleValidSubmit()
|
||||
{
|
||||
resultMessage = "";
|
||||
|
||||
if (mp4File == null || imageFile == null)
|
||||
{
|
||||
resultMessage = "Both MP4 and Image files are required.";
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting = true;
|
||||
|
||||
try
|
||||
{
|
||||
using var content = new MultipartFormDataContent();
|
||||
content.Add(new StringContent(uploadModel.Title ?? string.Empty), "Title");
|
||||
content.Add(new StringContent(uploadModel.Description ?? string.Empty), "Description");
|
||||
content.Add(new StringContent(uploadModel.Hashtags ?? string.Empty), "Hashtags");
|
||||
|
||||
// Add files
|
||||
var mp4Stream = mp4File.OpenReadStream(800 * 1024 * 1024); // 800 MB limit
|
||||
var mp4Content = new StreamContent(mp4Stream);
|
||||
mp4Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mp4File.ContentType);
|
||||
content.Add(mp4Content, "Mp4File", mp4File.Name);
|
||||
|
||||
var imageStream = imageFile.OpenReadStream(10 * 1024 * 1024); // 10 MB limit for images
|
||||
var imageContent = new StreamContent(imageStream);
|
||||
imageContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(imageFile.ContentType);
|
||||
content.Add(imageContent, "ImageFile", imageFile.Name);
|
||||
|
||||
// Call the API
|
||||
string url = $"{config["ApiUrl"]}/upload";
|
||||
var response = await http.PostAsync(url, content);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
resultMessage = "Files uploaded successfully!";
|
||||
nav.NavigateTo("/shorts");
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
resultMessage = $"Error: {response.StatusCode} - {error}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
resultMessage = $"An error occurred: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
OfflineDemo/Pages/ToDo.razor
Normal file
49
OfflineDemo/Pages/ToDo.razor
Normal file
@ -0,0 +1,49 @@
|
||||
@page "/todo"
|
||||
@inject ILocalStorageService localStorage
|
||||
|
||||
<h3>ToDo</h3>
|
||||
|
||||
@if (todos is not null)
|
||||
{
|
||||
<ol>
|
||||
@foreach (var todo in todos)
|
||||
{
|
||||
<li>@todo.ToDoItem (@todo.CreationDate.ToString("MMMM dd, yyyy hh:mm tt"))</li>
|
||||
}
|
||||
</ol>
|
||||
}
|
||||
|
||||
<h4>Add ToDo Item</h4>
|
||||
<EditForm Model="newTodo" OnValidSubmit="AddTodo">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
<div class="form-group">
|
||||
<label for="newTodo.ToDoItem">ToDo Item</label>
|
||||
<InputText id="newTodo.ToDoItem" class="form-control" @bind-Value="newTodo.ToDoItem" />
|
||||
<ValidationMessage For="@(() => newTodo.ToDoItem)" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
private List<TodoModel> todos;
|
||||
private TodoModel newTodo = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
todos = await localStorage.GetItemAsync<List<TodoModel>>("todos");
|
||||
|
||||
if (todos is null || todos.Count == 0)
|
||||
{
|
||||
todos = new();
|
||||
await localStorage.SetItemAsync("todos", todos);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddTodo()
|
||||
{
|
||||
todos.Add(newTodo);
|
||||
newTodo = new();
|
||||
await localStorage.SetItemAsync("todos", todos);
|
||||
}
|
||||
}
|
||||
19
OfflineDemo/Program.cs
Normal file
19
OfflineDemo/Program.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using OfflineDemo;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient());
|
||||
builder.Services.AddBlazoredLocalStorage();
|
||||
|
||||
builder.Services.Configure<KestrelServerOptions>(options =>
|
||||
{
|
||||
options.Limits.MaxRequestBodySize = 800L * 1024 * 1024; // 800 MB
|
||||
});
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
41
OfflineDemo/Properties/launchSettings.json
Normal file
41
OfflineDemo/Properties/launchSettings.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:28724",
|
||||
"sslPort": 44360
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5137",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7119;http://localhost:5137",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
OfflineDemo/_Imports.razor
Normal file
12
OfflineDemo/_Imports.razor
Normal file
@ -0,0 +1,12 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using OfflineDemo
|
||||
@using OfflineDemo.Layout
|
||||
@using Blazored.LocalStorage
|
||||
@using OfflineDemo.Models
|
||||
8
OfflineDemo/wwwroot/appsettings.json
Normal file
8
OfflineDemo/wwwroot/appsettings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Kestrel": {
|
||||
"Limits": {
|
||||
"MaxRequestBodySize": 838860800 // 800 MB in bytes
|
||||
}
|
||||
},
|
||||
"ApiUrl": "https://localhost:7099/api"
|
||||
}
|
||||
103
OfflineDemo/wwwroot/css/app.css
Normal file
103
OfflineDemo/wwwroot/css/app.css
Normal file
@ -0,0 +1,103 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #0071c1;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin: 20vh auto 1rem auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
7
OfflineDemo/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file
7
OfflineDemo/wwwroot/css/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
OfflineDemo/wwwroot/css/bootstrap/bootstrap.min.css.map
Normal file
1
OfflineDemo/wwwroot/css/bootstrap/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
BIN
OfflineDemo/wwwroot/favicon.png
Normal file
BIN
OfflineDemo/wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
OfflineDemo/wwwroot/icon-192.png
Normal file
BIN
OfflineDemo/wwwroot/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
OfflineDemo/wwwroot/icon-512.png
Normal file
BIN
OfflineDemo/wwwroot/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
37
OfflineDemo/wwwroot/index.html
Normal file
37
OfflineDemo/wwwroot/index.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>OfflineDemo</title>
|
||||
<base href="/" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link href="OfflineDemo.styles.css" rel="stylesheet" />
|
||||
<link href="manifest.webmanifest" rel="manifest" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<svg class="loading-progress">
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
<circle r="40%" cx="50%" cy="50%" />
|
||||
</svg>
|
||||
<div class="loading-progress-text"></div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
22
OfflineDemo/wwwroot/manifest.webmanifest
Normal file
22
OfflineDemo/wwwroot/manifest.webmanifest
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "OfflineDemo",
|
||||
"short_name": "OfflineDemo",
|
||||
"id": "./",
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#03173d",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "icon-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
}
|
||||
27
OfflineDemo/wwwroot/sample-data/weather.json
Normal file
27
OfflineDemo/wwwroot/sample-data/weather.json
Normal file
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"date": "2022-01-06",
|
||||
"temperatureC": 1,
|
||||
"summary": "Freezing"
|
||||
},
|
||||
{
|
||||
"date": "2022-01-07",
|
||||
"temperatureC": 14,
|
||||
"summary": "Bracing"
|
||||
},
|
||||
{
|
||||
"date": "2022-01-08",
|
||||
"temperatureC": -13,
|
||||
"summary": "Freezing"
|
||||
},
|
||||
{
|
||||
"date": "2022-01-09",
|
||||
"temperatureC": -16,
|
||||
"summary": "Balmy"
|
||||
},
|
||||
{
|
||||
"date": "2022-01-10",
|
||||
"temperatureC": -2,
|
||||
"summary": "Chilly"
|
||||
}
|
||||
]
|
||||
4
OfflineDemo/wwwroot/service-worker.js
Normal file
4
OfflineDemo/wwwroot/service-worker.js
Normal file
@ -0,0 +1,4 @@
|
||||
// In development, always fetch from the network and do not enable offline support.
|
||||
// This is because caching would make development more difficult (changes would not
|
||||
// be reflected on the first load after each change).
|
||||
self.addEventListener('fetch', () => { });
|
||||
55
OfflineDemo/wwwroot/service-worker.published.js
Normal file
55
OfflineDemo/wwwroot/service-worker.published.js
Normal file
@ -0,0 +1,55 @@
|
||||
// Caution! Be sure you understand the caveats before publishing an application with
|
||||
// offline support. See https://aka.ms/blazor-offline-considerations
|
||||
|
||||
self.importScripts('./service-worker-assets.js');
|
||||
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
|
||||
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
|
||||
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
|
||||
|
||||
const cacheNamePrefix = 'offline-cache-';
|
||||
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
|
||||
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
|
||||
const offlineAssetsExclude = [ /^service-worker\.js$/ ];
|
||||
|
||||
// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.
|
||||
const base = "/";
|
||||
const baseUrl = new URL(base, self.origin);
|
||||
const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href);
|
||||
|
||||
async function onInstall(event) {
|
||||
console.info('Service worker: Install');
|
||||
|
||||
// Fetch and cache all matching items from the assets manifest
|
||||
const assetsRequests = self.assetsManifest.assets
|
||||
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
|
||||
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
|
||||
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
|
||||
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
|
||||
}
|
||||
|
||||
async function onActivate(event) {
|
||||
console.info('Service worker: Activate');
|
||||
|
||||
// Delete unused caches
|
||||
const cacheKeys = await caches.keys();
|
||||
await Promise.all(cacheKeys
|
||||
.filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
|
||||
.map(key => caches.delete(key)));
|
||||
}
|
||||
|
||||
async function onFetch(event) {
|
||||
let cachedResponse = null;
|
||||
if (event.request.method === 'GET') {
|
||||
// For all navigation requests, try to serve index.html from cache,
|
||||
// unless that request is for an offline resource.
|
||||
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
|
||||
const shouldServeIndexHtml = event.request.mode === 'navigate'
|
||||
&& !manifestUrlList.some(url => url === event.request.url);
|
||||
|
||||
const request = shouldServeIndexHtml ? 'index.html' : event.request;
|
||||
const cache = await caches.open(cacheName);
|
||||
cachedResponse = await cache.match(request);
|
||||
}
|
||||
|
||||
return cachedResponse || fetch(event.request);
|
||||
}
|
||||
11
OfflineDemoApi/Data/ISqlDataAccess.cs
Normal file
11
OfflineDemoApi/Data/ISqlDataAccess.cs
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
namespace OfflineDemoApi.Data
|
||||
{
|
||||
public interface ISqlDataAccess
|
||||
{
|
||||
Task<List<T>> LoadData<T, U>(string storedProcedure, U parameters, string connectionStringName);
|
||||
Task SaveData<T>(string storedProcedure, T parameters, string connectionStringName);
|
||||
Task<T> SaveDataScalar<T, U>(string storedProcedure, U parameters, string connectionStringName);
|
||||
}
|
||||
}
|
||||
58
OfflineDemoApi/Data/SqlDataAccess.cs
Normal file
58
OfflineDemoApi/Data/SqlDataAccess.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Data;
|
||||
|
||||
namespace OfflineDemoApi.Data;
|
||||
|
||||
public class SqlDataAccess : ISqlDataAccess
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public SqlDataAccess(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<List<T>> LoadData<T, U>(string storedProcedure,
|
||||
U parameters,
|
||||
string connectionStringName)
|
||||
{
|
||||
string connectionString = _config.GetConnectionString(connectionStringName) ??
|
||||
throw new KeyNotFoundException("Did not find the connection string specified");
|
||||
|
||||
using IDbConnection connection = new SqlConnection(connectionString);
|
||||
|
||||
List<T> output = (await connection.QueryAsync<T>(
|
||||
storedProcedure,
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure)).ToList();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public async Task<T> SaveDataScalar<T, U>(string storedProcedure,
|
||||
U parameters,
|
||||
string connectionStringName)
|
||||
{
|
||||
string connectionString = _config.GetConnectionString(connectionStringName) ??
|
||||
throw new KeyNotFoundException("Did not find the connection string specified");
|
||||
|
||||
using IDbConnection connection = new SqlConnection(connectionString);
|
||||
|
||||
T? output = await connection.ExecuteScalarAsync<T>(storedProcedure, parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
return output ?? throw new ArgumentNullException("The return value was null, which is an invalid result.");
|
||||
}
|
||||
|
||||
public async Task SaveData<T>(string storedProcedure,
|
||||
T parameters,
|
||||
string connectionStringName)
|
||||
{
|
||||
string connectionString = _config.GetConnectionString(connectionStringName) ??
|
||||
throw new KeyNotFoundException("Did not find the connection string specified");
|
||||
|
||||
using IDbConnection connection = new SqlConnection(connectionString);
|
||||
|
||||
await connection.ExecuteAsync(storedProcedure, parameters, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
7
OfflineDemoApi/Models/AzureStorageConfig.cs
Normal file
7
OfflineDemoApi/Models/AzureStorageConfig.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OfflineDemoApi.Models;
|
||||
|
||||
public class AzureStorageConfig
|
||||
{
|
||||
public string? ConnectionString { get; set; }
|
||||
public string? ContainerName { get; set; }
|
||||
}
|
||||
12
OfflineDemoApi/Models/ShortsModel.cs
Normal file
12
OfflineDemoApi/Models/ShortsModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace OfflineDemoApi.Models;
|
||||
|
||||
public class ShortsModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Hashtags { get; set; }
|
||||
public string Mp4FileUrl { get; set; }
|
||||
public string ImageFileUrl { get; set; }
|
||||
public bool IsUploaded { get; set; } = false;
|
||||
}
|
||||
18
OfflineDemoApi/OfflineDemoApi.csproj
Normal file
18
OfflineDemoApi/OfflineDemoApi.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>9343a209-53dc-41cc-b263-bce9f7c1f999</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
145
OfflineDemoApi/Program.cs
Normal file
145
OfflineDemoApi/Program.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using Azure.Storage.Blobs;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using OfflineDemoApi.Data;
|
||||
using OfflineDemoApi.Models;
|
||||
using System;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
// Bind Azure Storage configuration
|
||||
var azureStorageConfig = builder.Configuration.GetSection("AzureStorage").Get<AzureStorageConfig>();
|
||||
|
||||
// Ensure the blob container exists
|
||||
var blobServiceClient = new BlobServiceClient(azureStorageConfig.ConnectionString);
|
||||
var blobContainerClient = blobServiceClient.GetBlobContainerClient(azureStorageConfig.ContainerName);
|
||||
await blobContainerClient.CreateIfNotExistsAsync();
|
||||
await blobContainerClient.SetAccessPolicyAsync(Azure.Storage.Blobs.Models.PublicAccessType.None);
|
||||
|
||||
builder.Services.AddSingleton(blobContainerClient);
|
||||
|
||||
// Configure Kestrel limits
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
options.Limits.MaxRequestBodySize = 524288000*2; // Example: 500 MB
|
||||
options.Limits.MaxRequestBufferSize = 524288000 * 2; // Example: 100 MB
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<ISqlDataAccess, SqlDataAccess>();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAnyOrigin", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseCors("AllowAnyOrigin");
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.MapGet("/", () => {
|
||||
return "Hello World";
|
||||
});
|
||||
|
||||
app.MapGet("/api/shorts", async (ISqlDataAccess sql) =>
|
||||
{
|
||||
return await sql.LoadData<ShortsModel, dynamic>("spShorts_GetAll", new { }, "sql");
|
||||
});
|
||||
|
||||
app.MapGet("/api/file", async (IConfiguration config, string url) =>
|
||||
{
|
||||
var blobUri = new Uri(url);
|
||||
var storageAccountKey = config.GetValue<string>("AzureStorage:StorageAccountKey");
|
||||
var storageAccountName = config.GetValue<string>("AzureStorage:StorageAccountName");
|
||||
|
||||
var credential = new Azure.Storage.StorageSharedKeyCredential(storageAccountName, storageAccountKey);
|
||||
var blobClient = new BlobClient(blobUri, credential);
|
||||
|
||||
var downloadResponse = await blobClient.DownloadStreamingAsync();
|
||||
return Results.File(
|
||||
downloadResponse.Value.Content,
|
||||
downloadResponse.Value.Details.ContentType,
|
||||
fileDownloadName: blobUri.Segments.Last()
|
||||
);
|
||||
});
|
||||
|
||||
app.MapPost("/api/upload", async (HttpRequest request,
|
||||
BlobContainerClient containerClient,
|
||||
ISqlDataAccess sql) =>
|
||||
{
|
||||
if (!request.HasFormContentType || request.Form.Files.Count == 0)
|
||||
{
|
||||
return Results.BadRequest("Invalid form submission. Files are required.");
|
||||
}
|
||||
|
||||
var form = await request.ReadFormAsync();
|
||||
|
||||
// Extract form data
|
||||
var title = form["Title"].ToString();
|
||||
var description = form["Description"].ToString();
|
||||
var hashtags = form["Hashtags"].ToString();
|
||||
var mp4File = form.Files["Mp4File"];
|
||||
var imageFile = form.Files["ImageFile"];
|
||||
|
||||
if (mp4File == null || imageFile == null)
|
||||
{
|
||||
return Results.BadRequest("Both MP4 and image files are required.");
|
||||
}
|
||||
|
||||
// Validate file size
|
||||
const long maxFileSize = 800L * 1024 * 1024; // 800MB
|
||||
if (mp4File.Length > maxFileSize)
|
||||
{
|
||||
return Results.BadRequest("MP4 file exceeds the 800MB limit.");
|
||||
}
|
||||
|
||||
var createNewParameters = new { title, description, hashtags };
|
||||
int id = await sql.SaveDataScalar<int, dynamic>("spShorts_CreateNew", createNewParameters, "sql");
|
||||
|
||||
// Rename files
|
||||
string? mp4FileName = $"{id}.mp4";
|
||||
|
||||
// TODO - Fix this extension lookup
|
||||
string? imageFileName = $"{id}.{imageFile.FileName.Split('.')[1]}";
|
||||
|
||||
// Upload MP4 file
|
||||
var mp4BlobClient = containerClient.GetBlobClient($"shorts/{mp4FileName}");
|
||||
await using (var mp4Stream = mp4File.OpenReadStream())
|
||||
{
|
||||
await mp4BlobClient.UploadAsync(mp4Stream, true);
|
||||
}
|
||||
|
||||
// Upload Image file
|
||||
var imageBlobClient = containerClient.GetBlobClient($"images/{imageFileName}");
|
||||
await using (var imageStream = imageFile.OpenReadStream())
|
||||
{
|
||||
await imageBlobClient.UploadAsync(imageStream, true);
|
||||
}
|
||||
|
||||
// Update SQL with the uploaded files
|
||||
string? mp4FileUrl = mp4BlobClient.Uri.ToString();
|
||||
string? imageFileUrl = imageBlobClient.Uri.ToString();
|
||||
|
||||
var addUploadedFilesParameters = new { id, mp4FileUrl, imageFileUrl };
|
||||
|
||||
await sql.SaveData("spShorts_AddUploadedFiles", addUploadedFilesParameters, "sql");
|
||||
|
||||
return Results.Ok(new { Message = "Files uploaded to Azure Blob Storage successfully." });
|
||||
});
|
||||
|
||||
app.Run();
|
||||
@ -0,0 +1,173 @@
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"metadata": {
|
||||
"_dependencyType": "compute.function.linux.appService"
|
||||
},
|
||||
"parameters": {
|
||||
"resourceGroupName": {
|
||||
"type": "string",
|
||||
"defaultValue": "offline-demo-sql",
|
||||
"metadata": {
|
||||
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
|
||||
}
|
||||
},
|
||||
"resourceGroupLocation": {
|
||||
"type": "string",
|
||||
"defaultValue": "southcentralus",
|
||||
"metadata": {
|
||||
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
|
||||
}
|
||||
},
|
||||
"resourceName": {
|
||||
"type": "string",
|
||||
"defaultValue": "offlinedemoapi",
|
||||
"metadata": {
|
||||
"description": "Name of the main resource to be created by this template."
|
||||
}
|
||||
},
|
||||
"resourceLocation": {
|
||||
"type": "string",
|
||||
"defaultValue": "[parameters('resourceGroupLocation')]",
|
||||
"metadata": {
|
||||
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"type": "Microsoft.Resources/resourceGroups",
|
||||
"name": "[parameters('resourceGroupName')]",
|
||||
"location": "[parameters('resourceGroupLocation')]",
|
||||
"apiVersion": "2019-10-01"
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.Resources/deployments",
|
||||
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
|
||||
"resourceGroup": "[parameters('resourceGroupName')]",
|
||||
"apiVersion": "2019-10-01",
|
||||
"dependsOn": [
|
||||
"[parameters('resourceGroupName')]"
|
||||
],
|
||||
"properties": {
|
||||
"mode": "Incremental",
|
||||
"expressionEvaluationOptions": {
|
||||
"scope": "inner"
|
||||
},
|
||||
"parameters": {
|
||||
"resourceGroupName": {
|
||||
"value": "[parameters('resourceGroupName')]"
|
||||
},
|
||||
"resourceGroupLocation": {
|
||||
"value": "[parameters('resourceGroupLocation')]"
|
||||
},
|
||||
"resourceName": {
|
||||
"value": "[parameters('resourceName')]"
|
||||
},
|
||||
"resourceLocation": {
|
||||
"value": "[parameters('resourceLocation')]"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"resourceGroupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceGroupLocation": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceLocation": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]",
|
||||
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
|
||||
"storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]",
|
||||
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]",
|
||||
"function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"location": "[parameters('resourceLocation')]",
|
||||
"name": "[parameters('resourceName')]",
|
||||
"type": "Microsoft.Web/sites",
|
||||
"apiVersion": "2015-08-01",
|
||||
"tags": {
|
||||
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
|
||||
},
|
||||
"dependsOn": [
|
||||
"[variables('appServicePlan_ResourceId')]",
|
||||
"[variables('storage_ResourceId')]"
|
||||
],
|
||||
"kind": "functionapp",
|
||||
"properties": {
|
||||
"name": "[parameters('resourceName')]",
|
||||
"kind": "functionapp",
|
||||
"httpsOnly": true,
|
||||
"reserved": false,
|
||||
"serverFarmId": "[variables('appServicePlan_ResourceId')]",
|
||||
"siteConfig": {
|
||||
"alwaysOn": true,
|
||||
"linuxFxVersion": "dotnet|3.1"
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"type": "SystemAssigned"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "appsettings",
|
||||
"type": "config",
|
||||
"apiVersion": "2015-08-01",
|
||||
"dependsOn": [
|
||||
"[variables('function_ResourceId')]"
|
||||
],
|
||||
"properties": {
|
||||
"AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]",
|
||||
"FUNCTIONS_EXTENSION_VERSION": "~3",
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"location": "[parameters('resourceGroupLocation')]",
|
||||
"name": "[variables('storage_name')]",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"apiVersion": "2017-10-01",
|
||||
"tags": {
|
||||
"[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty"
|
||||
},
|
||||
"properties": {
|
||||
"supportsHttpsTrafficOnly": true
|
||||
},
|
||||
"sku": {
|
||||
"name": "Standard_LRS"
|
||||
},
|
||||
"kind": "Storage"
|
||||
},
|
||||
{
|
||||
"location": "[parameters('resourceGroupLocation')]",
|
||||
"name": "[variables('appServicePlan_name')]",
|
||||
"type": "Microsoft.Web/serverFarms",
|
||||
"apiVersion": "2015-02-01",
|
||||
"kind": "linux",
|
||||
"properties": {
|
||||
"name": "[variables('appServicePlan_name')]",
|
||||
"sku": "Standard",
|
||||
"workerSizeId": "0",
|
||||
"reserved": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
23
OfflineDemoApi/Properties/launchSettings.json
Normal file
23
OfflineDemoApi/Properties/launchSettings.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7099;http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
OfflineDemoApi/appsettings.Development.json
Normal file
8
OfflineDemoApi/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
OfflineDemoApi/appsettings.json
Normal file
18
OfflineDemoApi/appsettings.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"AzureStorage": {
|
||||
"ConnectionString": "",
|
||||
"ContainerName": "shorts",
|
||||
"StorageAccountName": "",
|
||||
"StorageAccountKey": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"sql": ""
|
||||
}
|
||||
}
|
||||
39
OfflineDemoApp.sln
Normal file
39
OfflineDemoApp.sln
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34322.80
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OfflineDemo", "OfflineDemo\OfflineDemo.csproj", "{D519B7EB-0939-4288-ACFF-35901F8BF6A3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OfflineDemoApi", "OfflineDemoApi\OfflineDemoApi.csproj", "{D79F7FAB-7778-4D3E-8F55-7C352B26E564}"
|
||||
EndProject
|
||||
Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "OfflineDemoSql", "OfflineDemoSql\OfflineDemoSql.sqlproj", "{4E520D7D-BE86-466C-A70F-4B69BE5C698B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D519B7EB-0939-4288-ACFF-35901F8BF6A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D519B7EB-0939-4288-ACFF-35901F8BF6A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D519B7EB-0939-4288-ACFF-35901F8BF6A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D519B7EB-0939-4288-ACFF-35901F8BF6A3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D79F7FAB-7778-4D3E-8F55-7C352B26E564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D79F7FAB-7778-4D3E-8F55-7C352B26E564}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D79F7FAB-7778-4D3E-8F55-7C352B26E564}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D79F7FAB-7778-4D3E-8F55-7C352B26E564}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4E520D7D-BE86-466C-A70F-4B69BE5C698B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E520D7D-BE86-466C-A70F-4B69BE5C698B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E520D7D-BE86-466C-A70F-4B69BE5C698B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{4E520D7D-BE86-466C-A70F-4B69BE5C698B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4E520D7D-BE86-466C-A70F-4B69BE5C698B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4E520D7D-BE86-466C-A70F-4B69BE5C698B}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {35585129-469B-4F71-AE66-BBC84B45E9D8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
66
OfflineDemoSql/OfflineDemoSql.sqlproj
Normal file
66
OfflineDemoSql/OfflineDemoSql.sqlproj
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<Name>OfflineDemoSql</Name>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectVersion>4.1</ProjectVersion>
|
||||
<ProjectGuid>{4e520d7d-be86-466c-a70f-4b69be5c698b}</ProjectGuid>
|
||||
<DSP>Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider</DSP>
|
||||
<OutputType>Database</OutputType>
|
||||
<RootPath>
|
||||
</RootPath>
|
||||
<RootNamespace>OfflineDemoSql</RootNamespace>
|
||||
<AssemblyName>OfflineDemoSql</AssemblyName>
|
||||
<ModelCollation>1033, CI</ModelCollation>
|
||||
<DefaultFileStructure>BySchemaAndSchemaType</DefaultFileStructure>
|
||||
<DeployToDatabase>True</DeployToDatabase>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<TargetLanguage>CS</TargetLanguage>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<SqlServerVerification>False</SqlServerVerification>
|
||||
<IncludeCompositeObjects>True</IncludeCompositeObjects>
|
||||
<TargetDatabaseSet>True</TargetDatabaseSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<BuildScriptName>$(MSBuildProjectName).sql</BuildScriptName>
|
||||
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<DefineDebug>false</DefineDebug>
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<BuildScriptName>$(MSBuildProjectName).sql</BuildScriptName>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<DefineDebug>true</DefineDebug>
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
|
||||
<!-- Default to the v11.0 targets path if the targets file for the current VS version is not found -->
|
||||
<SSDTExists Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets')">True</SSDTExists>
|
||||
<VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Build Include="Shorts.sql" />
|
||||
<Build Include="spShorts_CreateNew.sql" />
|
||||
<Build Include="spShorts_AddUploadedFiles.sql" />
|
||||
<Build Include="spShorts_GetAll.sql" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
10
OfflineDemoSql/Shorts.sql
Normal file
10
OfflineDemoSql/Shorts.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE [dbo].[Shorts]
|
||||
(
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Title NVARCHAR(100) NOT NULL,
|
||||
Description NVARCHAR(300),
|
||||
Hashtags NVARCHAR(75),
|
||||
Mp4FileUrl NVARCHAR(1024),
|
||||
ImageFileUrl NVARCHAR(1024),
|
||||
[IsUploaded] BIT NOT NULL DEFAULT 0
|
||||
)
|
||||
10
OfflineDemoSql/spShorts_AddUploadedFiles.sql
Normal file
10
OfflineDemoSql/spShorts_AddUploadedFiles.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE PROCEDURE [dbo].[spShorts_AddUploadedFiles]
|
||||
@id int,
|
||||
@mp4FileUrl nvarchar(1024),
|
||||
@imageFileUrl nvarchar(1024)
|
||||
AS
|
||||
begin
|
||||
update dbo.Shorts
|
||||
set Mp4FileUrl = @mp4FileUrl, ImageFileUrl = @imageFileUrl
|
||||
where Id = @id;
|
||||
end
|
||||
11
OfflineDemoSql/spShorts_CreateNew.sql
Normal file
11
OfflineDemoSql/spShorts_CreateNew.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE PROCEDURE [dbo].[spShorts_CreateNew]
|
||||
@title nvarchar(100),
|
||||
@description nvarchar(300),
|
||||
@hashtags nvarchar(75)
|
||||
AS
|
||||
begin
|
||||
insert into dbo.Shorts (Title, [Description], Hashtags)
|
||||
values (@title, @description, @hashtags);
|
||||
|
||||
select cast(SCOPE_IDENTITY() as int);
|
||||
end
|
||||
7
OfflineDemoSql/spShorts_GetAll.sql
Normal file
7
OfflineDemoSql/spShorts_GetAll.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE PROCEDURE [dbo].[spShorts_GetAll]
|
||||
|
||||
AS
|
||||
begin
|
||||
select [Id], [Title], [Description], [Hashtags], [Mp4FileUrl], [ImageFileUrl], [IsUploaded]
|
||||
from dbo.Shorts;
|
||||
end
|
||||
Reference in New Issue
Block a user