Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Készítette : Rutger Storm
ASP.NET Core támogatja egy vagy több fájl feltöltését pufferelt modellkötés használatával a kisebb fájlokhoz, valamint a nagyobb fájlok nem kazettás streamelését.
Mintakód megtekintése vagy letöltése (hogyan töltsd le)
Biztonsági szempontok
Legyen óvatos, amikor engedélyezik a felhasználóknak a fájlok feltöltését a kiszolgálóra. A kiberbűnözők megpróbálhatják:
- Szolgáltatásmegtagadási támadások végrehajtása.
- Töltsön fel vírusokat vagy kártevőket.
- A hálózatokat és a kiszolgálókat más módon is veszélyeztetheti.
A sikeres támadás valószínűségét csökkentő biztonsági lépések a következők:
- Fájlok feltöltése dedikált fájlfeltöltési területre, lehetőleg nem rendszermeghajtóra. A dedikált helyek megkönnyítik a feltöltött fájlok biztonsági korlátozását. A fájlfeltöltési hely végrehajtási engedélyeinek letiltása.†
- Ne őrizze meg a feltöltött fájlokat ugyanabban a könyvtárfában, mint az app.†
- Használjon az alkalmazás által meghatározott biztonságos fájlnevet. Ne használjon a felhasználó által megadott fájlnevet vagy a feltöltött fájl nem megbízható fájlnevét.† a HTML kódolja a nem megbízható fájlnevet a megjelenítéskor. Például naplózhatja a fájl nevét, vagy megjelenítheti a felhasználói felületen (Razor a html automatikusan kódolja a kimenetet).
- Csak jóváhagyott fájlkiterjesztések engedélyezése az alkalmazás tervezési specifikációihoz.†
- Ellenőrizze, hogy az ügyféloldali ellenőrzések a szerveren történnek-e. Az ügyféloldali ellenőrzések könnyen megkerülhetők.
- Ellenőrizze a feltöltött fájl méretét. Állítson be maximális méretkorlátot a nagyméretű feltöltések elkerülése érdekében.
- Ha egy feltöltött fájl nem írhatja felül a fájlokat ugyanazzal a névvel, a fájl feltöltése előtt ellenőrizze a fájl nevét az adatbázison vagy a fizikai tárolón.
- A fájl tárolása előtt futtasson vírus-/kártevőolvasót a feltöltött tartalomon.
†A mintaalkalmazás egy olyan megközelítést mutat be, amely megfelel a feltételeknek.
Warning
A rosszindulatú kódok rendszerbe való feltöltése gyakran az első lépés a kód végrehajtásához, amely a következőre képes:
- Teljesen átveheti a rendszer irányítását.
- A rendszer túlterhelése azzal az eredménnyel, hogy a rendszer összeomlik.
- Felhasználói vagy rendszeradatok megsértése.
- Graffiti alkalmazása nyilvános felhasználói felületre.
A felhasználóktól érkező fájlok elfogadásakor fellépő biztonsági rések csökkentéséről az alábbi forrásokból tájékozódhat:
A biztonsági intézkedések implementálásával kapcsolatos további információkért, beleértve a mintaalkalmazásból származó példákat, tekintse meg az Ellenőrzés szakaszt.
Tárolási forgatókönyvek
A fájlok tárolási lehetőségei a következők:
Database
- Kis méretű fájlfeltöltések esetén az adatbázisok gyakran gyorsabbak, mint a fizikai tárolás (fájlrendszer vagy hálózati megosztás) beállításai.
- Az adatbázisok gyakran kényelmesebbek, mint a fizikai tárolási lehetőségek, mivel a felhasználói adatok adatbázisrekordjának lekérése egyidejűleg megadhatja a fájl tartalmát (például avatarképet).
- Az adatbázisok potenciálisan olcsóbbak, mint egy felhőalapú adattárolási szolgáltatás használata.
Fizikai tárolás (fájlrendszer vagy hálózati megosztás)
- Nagyméretű fájlfeltöltések esetén:
- Az adatbázis korlátai korlátozhatják a feltöltés méretét.
- A fizikai tárolás gyakran kevésbé gazdaságos, mint egy adatbázisban való tárolás.
- A fizikai tárolás potenciálisan olcsóbb, mint egy felhőalapú adattárolási szolgáltatás használata.
- Az alkalmazás folyamatának olvasási és írási engedélyekkel kell rendelkeznie a tárolási helyre. Soha ne adjon végrehajtási engedélyt.
- Nagyméretű fájlfeltöltések esetén:
Felhőalapú adattárolási szolgáltatás, például Azure Blob Storage.
- A szolgáltatások általában jobb méretezhetőséget és rugalmasságot biztosítanak a helyszíni megoldásokkal szemben, amelyek általában egyetlen meghibásodási pontnak vannak kitéve.
- A szolgáltatások költségei potenciálisan alacsonyabbak lehetnek nagy tárolási infrastruktúra esetében.
További információ : Rövid útmutató: Blob létrehozása az objektumtárolóban a .NET használatával.
Kis és nagy fájlok
A kis és nagy fájlok definíciója a rendelkezésre álló számítási erőforrásoktól függ. Az alkalmazásoknak az elvárt méretek kezeléséhez használt tárolási megközelítést kell viszonyítani. Teljesítményteszt memória, PROCESSZOR, lemez és adatbázis teljesítménye.
Bár az üzembe helyezéshez szükséges kicsi és nagy méreteknél nem lehet konkrét határokat megadni, az alábbiakban bemutatjuk az ASP.NET Core kapcsolódó alapértelmezett beállításait FormOptions (API-dokumentáció):
- Alapértelmezés szerint
HttpRequest.Formnem puffereli a teljes kérelemtörzset (BufferBody), de a benne lévő többrészes űrlapfájlokat is puffereli. - MultipartBodyLengthLimit a pufferelt űrlapfájlok maximális mérete (alapértelmezett: 128 MB).
-
MemoryBufferThreshold A memória pufferelési küszöbértékét jelzi, mielőtt a lemezen lévő pufferfájlra vált (alapértelmezett: 64 KB).
MemoryBufferThresholdhatárként szolgál a kis és a nagy fájlok között, amelyet az alkalmazás erőforrásaitól és forgatókönyveitől függően emelnek vagy csökkentenek.
További információért tekintse meg az ASP.NET Core referencia forráskódjának FormOptions osztályát FormOptions.
Note
A .NET referenciaforrásra mutató dokumentációs hivatkozások általában betöltik az adattár alapértelmezett ágát, amely a .NET következő kiadásának aktuális fejlesztését jelöli. Egy adott kiadás címkéjének kiválasztásához használja az Ágak vagy címkék közötti váltás legördülő listát. További információ: A ASP.NET Core-forráskód (dotnet/AspNetCore.Docs #26205) verziócímkéjének kiválasztása.
Fájlfeltöltési forgatókönyvek
A fájlok feltöltésének két általános módszere a pufferelés és a streamelés.
Buffering
A teljes fájlt beolvassák egy IFormFile-ba.
IFormFile A fájl feldolgozásához vagy mentéséhez használt fájl C#-reprezentációja.
A fájlfeltöltések által használt lemez és memória az egyidejű fájlfeltöltések számától és méretétől függ. Ha egy alkalmazás túl sok feltöltést próbál pufferelni, a webhely összeomlik, amikor elfogy a memória vagy a lemezterület. Ha a fájlfeltöltések mérete vagy gyakorisága kimeríti az alkalmazás erőforrásait, használjon streamelést.
A rendszer minden 64 KB-nál nagyobb pufferelt fájlt áthelyez a memóriából egy lemezen lévő ideiglenes fájlba.
A rendszer a nagyobb kérelmek ideiglenes fájljait a környezeti változóban ASPNETCORE_TEMP megnevezett helyre írja. Ha ASPNETCORE_TEMP nincs definiálva, a rendszer az aktuális felhasználó ideiglenes mappájába írja a fájlokat.
A kis fájlok pufferelését a témakör alábbi szakaszai ismertetik:
Streaming
A fájl többrészes kérelemből érkezik, és az alkalmazás közvetlenül dolgozza fel vagy menti. A streamelés nem javítja jelentősen a teljesítményt. A streamelés csökkenti a memória vagy a lemezterület iránti igényeket a fájlok feltöltésekor.
A nagy fájlok streamelése a Nagy fájlok feltöltése streamelési szakaszában található.
Kis méretű fájlok feltöltése pufferelt modellkötéssel a fizikai tárolóba
Kis fájlok feltöltéséhez használjon többrészes űrlapot, vagy hozzon létre egy POST-kérést JavaScript használatával.
Az alábbi példa bemutatja, hogy egy Razor Pages-űrlapot használ-e egyetlen fájl feltöltésére (Pages/BufferedSingleFileUploadPhysical.cshtml a mintaalkalmazásban):
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file" />
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
Az alábbi példa az előző példához hasonló, azzal a kivételével, hogy:
- A JavaScript (Fetch API) az űrlap adatainak elküldésére szolgál.
- Nincs ellenőrzés.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
A POST űrlap JavaScriptben való végrehajtásához olyan ügyfelek esetében, amelyek nem támogatják a Fetch API-t, használja az alábbi módszerek egyikét:
Használjon beolvasási polifillt (például window.fetch polyfill (github/fetch)).
Használja a
XMLHttpRequest. Például:<script> "use strict"; function AJAXSubmit (oFormElement) { var oReq = new XMLHttpRequest(); oReq.onload = function(e) { oFormElement.elements.namedItem("result").value = 'Result: ' + this.status + ' ' + this.statusText; }; oReq.open("post", oFormElement.action); oReq.send(new FormData(oFormElement)); } </script>
A fájlfeltöltések támogatásához a HTML-űrlapoknak meg kell adnia a kódolási típust (enctype) multipart/form-data.
Ha egy files bemeneti elem támogatja a több fájl feltöltését, adja meg az multiple elem attribútumát <input> :
<input asp-for="FileUpload.FormFiles" type="file" multiple />
A kiszolgálóra feltöltött egyes fájlok a Modellkötés funkcióval IFormFileérhetők el. A mintaalkalmazás több pufferelt fájlfeltöltést mutat be adatbázis- és fizikai tárolási forgatókönyvekhez.
Warning
Ne használja a FileName tulajdonságot másra, mint megjelenítésre és naplózásra. Megjelenítéskor vagy naplózáskor a HTML kódolja a fájlnevet. A kiberbűnözők rosszindulatú fájlnevet adhatnak meg, beleértve a teljes elérési utakat vagy a relatív elérési utakat. Az alkalmazásoknak a következőnek kell lennie:
- Távolítsa el az elérési utat a felhasználó által megadott fájlnévből.
- Mentse a HTML-kódolású, elérési út által eltávolított fájlnevet a felhasználói felülethez vagy a naplózáshoz.
- Hozzon létre egy új véletlenszerű fájlnevet a tárolóhoz.
A következő kód eltávolítja az elérési utat a fájlnévből:
string untrustedFileName = Path.GetFileName(pathName);
Az eddig megadott példák nem veszik figyelembe a biztonsági szempontokat. További információt a következő szakaszok és a mintaalkalmazás nyújt:
Amikor modellkötéssel tölt fel fájlokat, a IFormFileműveletmetódus a következőt tudja elfogadni:
- Egyetlen IFormFile.
- A következő gyűjtemények bármelyike, amelyek több fájlt jelölnek:
Note
Az összekapcsolás név szerint hozzárendeli az űrlapfájlokat. A HTML name értéknek a <input type="file" name="formFile"> elemen meg kell egyeznie a C# paraméterhez/tulajdonsághoz kötött (FormFile) értékkel. Lásd a POST metódus paraméternevének hozzárendelése az attribútumértékhez című szakaszt további információért.
A következő példa:
- Ismétlődően végigmegy egy vagy több feltöltött fájlon.
- A Path.GetTempFileName használatával egy fájl teljes elérési útját adja vissza, beleértve a fájl nevét is.
- Az alkalmazás által létrehozott fájlnév használatával menti a fájlokat a helyi fájlrendszerbe.
- A feltöltött fájlok teljes számát és méretét adja vissza.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Elérési út nélküli fájlnév létrehozásához használható Path.GetRandomFileName . Az alábbi példában az elérési út a konfigurációból származik:
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
A megadott FileStream elérési útnak tartalmaznia kell a fájlnevet. Ha a fájlnév nincs megadva, a rendszer futásidőben dob egy UnauthorizedAccessException nevet.
A technikával feltöltött fájlokat a IFormFile rendszer a feldolgozás előtt puffereli a memóriába vagy a kiszolgálón lévő lemezre. A műveletmetóduson belül a IFormFile tartalom elérhető, mint egy Stream. A helyi fájlrendszeren kívül fájlokat is menthet egy hálózati megosztásba vagy egy fájltároló szolgáltatásba, például az Azure Blob Storage-ba.
Egy másik példa, amely több fájlt hurkol a feltöltéshez, és biztonságos fájlneveket használ, lásd Pages/BufferedMultipleFileUploadPhysical.cshtml.cs a mintaalkalmazásban.
Warning
A .NET 7-es vagy korábbi verzióiban a Path.GetTempFileName olyan hibát jelez IOException , amikor több mint 65 535 fájl jön létre a korábbi ideiglenes fájlok törlése nélkül. A 65 535 fájl maximális száma kiszolgálónkénti korlát. A Windows operációs rendszerre vonatkozó korlátról további információt az alábbi cikkekben talál:
Kis méretű fájlok feltöltése pufferelt modellkötéssel egy adatbázisba
Ha bináris fájladatokat szeretne tárolni egy adatbázisban az Entity Framework használatával, definiáljon egy Byte tömbtulajdonságot az entitáson:
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Adja meg a következőt tartalmazó osztály lapmodell-tulajdonságát IFormFile:
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Note
IFormFile közvetlenül műveletmetódus-paraméterként vagy kötött modelltulajdonságként használható. Az előző példa egy kötött modelltulajdonságot használ.
A FileUpload a Razor Lapok űrlapban használatos.
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Amikor az űrlap küldve van a kiszolgálóra, másolja a IFormFile egy streambe, és mentse el egy bájttömbként az adatbázisban. Az alábbi példában _dbContext az alkalmazás adatbázis-környezetét tárolja:
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
Az előző példa hasonló a mintaalkalmazásban bemutatott forgatókönyvhöz:
Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml.cs
Warning
A bináris adatok relációs adatbázisokban való tárolásakor körültekintően kell eljárni, mert az hátrányosan befolyásolhatja a teljesítményt.
Ne támaszkodjon és ne bízzon a FileName tulajdonságban a IFormFile esetében ellenőrzés nélkül. A FileName tulajdonság csak megjelenítési célokra és csak HTML-kódolás után használható.
A megadott példák nem veszik figyelembe a biztonsági szempontokat. További információt a következő szakaszok és a mintaalkalmazás nyújt:
Nagyméretű fájlok feltöltése streameléssel
Olyan esetekben, amikor nagy méretű fájlfeltöltésre van szükség, a streamelt feltöltések lehetővé teszik a bejövő többrészes űrlapadatok közvetlen feldolgozását anélkül, hogy a teljes fájlt a memóriában vagy a lemezen puffereli a modellkötésen keresztül. Ez a technika különösen fontos olyan fájlok esetében, amelyek túlléphetik a kiszolgáló vagy a keretrendszer pufferelési küszöbértékeit.
A 9.x-hez készült mintaalkalmazás bemutatja, hogyan fogadhat egy kiszolgáló egy fájlt, és hogyan streamelheti az adatokat közvetlenül a lemezre, támogatva a hatékony törlést a HTTP-kérés lemondási jogkivonatán keresztül.
MultipartReader egy ASP.NET Core segédprogram a bejövő kérésekből származó fájlok olvasásához. Az alábbi kódrészlet bemutatja, hogyan lehet feldolgozni a kérést és streamelni a fájlt egy outputStream (például egy FileStream):
// Read the boundary from the Content-Type header
var boundary = HeaderUtilities.RemoveQuotes(
MediaTypeHeaderValue.Parse(request.ContentType).Boundary).Value;
// Use MultipartReader to stream data to a destination
var reader = new MultipartReader(boundary, Request.Body);
MultipartSection? section;
while ((section = await reader.ReadNextSectionAsync(cancellationToken)) != null)
{
var contentDisposition = section.GetContentDispositionHeader();
if (contentDisposition != null && contentDisposition.IsFileDisposition())
{
await section.Body.CopyToAsync(outputStream, cancellationToken);
}
}
IFormFeature egy olyan burkoló MultipartReader körül, ami nem igényli a kérés törzsének manuális elemzőkódját. A metódus használatával ReadFormAsync feltöltheti a kérelem űrlapadatait, majd hozzáférhet a feltöltött fájlokhoz a beépített gyűjteményből:
// Get the IFormFeature and read the form
var formFeature = Request.HttpContext.Features.GetRequiredFeature<IFormFeature>();
await formFeature.ReadFormAsync(cancellationToken);
// Access the uploaded file (example: first file)
var filePath = Request.Form.Files.First().FileName;
return Results.Ok($"Saved file at {filePath}");
Speciális forgatókönyvek esetén manuálisan elemezze a nyers kérelem törzsét HttpRequest.BodyReader, amely IPipeReader alacsony szintű, nagy teljesítményű streamelést kínál. A mintaalkalmazás olyan végpontkezelőket tartalmaz, amelyek minimális API-kban és vezérlőkben egyaránt használhatók IPipeReader .
A 3.1-s példa bemutatja, hogyan streamelhet egy fájlt egy vezérlőműveletbe JavaScript használatával. A fájl antiforgery jogkivonata egy egyedi szűrőattribútum használatával jön létre, és az ügyfél HTTP-fejléceibe kerül továbbításra a kérelem törzse helyett. Mivel a műveletmetódus közvetlenül dolgozza fel a feltöltött adatokat, az űrlapmodell kötését egy másik egyéni szűrő letiltja. A műveleten belül az űrlap tartalma egy MultipartReader segítségével olvasható be, amely minden egyes MultipartSection-et feldolgoz, vagy a tartalmat megfelelő módon tárolja. A többrészes szakaszok beolvasása után a művelet végrehajtja a saját modellkötését.
A kezdeti lap válasza betölti az űrlapot, és ment egy antiforgery tokent ( cookie az GenerateAntiforgeryTokenCookieAttribute attribútumon keresztül). Az attribútum az ASP.NET Core beépített antiforgery-támogatásával állít be egy cookie kérési token-t.
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
A DisableFormValueModelBindingAttribute modellkötés letiltására szolgál:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
A mintaalkalmazásban a GenerateAntiforgeryTokenCookieAttribute és a DisableFormValueModelBindingAttribute szűrőkként vannak alkalmazva a /StreamedSingleFileUploadDb oldalalkalmazási modelleken, mint például a /StreamedSingleFileUploadPhysical és a Startup.ConfigureServices, a Razor Pages-konvenciók használatával.
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});
Mivel a modellkötés nem olvassa be az űrlapot, az űrlapról kötött paraméterek nem kötnek (a lekérdezés, az útvonal és az élőfej továbbra is működik). A műveletmetódus közvetlenül a Request tulajdonsággal működik. Az A MultipartReader az egyes szakaszok olvasására szolgál. Kulcs-/értékadatok egy KeyValueAccumulator-ben vannak tárolva a rendszerben. A többrészes szakaszok beolvasása után a rendszer az KeyValueAccumulator űrlapadatokat modelltípushoz köti.
Az adatbázisba való streamelés teljes StreamingController.UploadDatabase módszere a következővel EF Core:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
A fizikai helyre történő streamelés teljes StreamingController.UploadPhysical módja:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
A mintaalkalmazásban az érvényesítési ellenőrzéseket a FileHelpers.ProcessStreamedFilerendszer kezeli.
Validation
A mintaalkalmazás osztálya FileHelpers számos ellenőrzést mutat be a pufferelt IFormFile és streamelt fájlfeltöltések esetében. A pufferelt fájlfeltöltések a mintaalkalmazásban való feldolgozásához IFormFile tekintse meg a ProcessFormFile fájl metódusát Utilities/FileHelpers.cs . A streamelt fájlok feldolgozásához tekintse meg a ProcessStreamedFile metódust ugyanabban a fájlban.
Warning
A mintaalkalmazásban bemutatott érvényesítési feldolgozási módszerek nem ellenőrzik a feltöltött fájlok tartalmát. A legtöbb éles forgatókönyvben egy vírus-/kártevőolvasó API-t használnak a fájlon, mielőtt elérhetővé tennék a fájlt a felhasználók vagy más rendszerek számára.
Bár a témakörminta az érvényesítési technikákat szemlélteti, ne implementálja az FileHelpers osztályt egy éles alkalmazásban, kivéve, ha:
- A megvalósítás teljes körű megértése.
- Módosítsa a megvalósítást az alkalmazás környezetének és specifikációinak megfelelően.
Soha ne implementáljon megkülönböztetés nélkül biztonsági kódot egy alkalmazásban anélkül, hogy megfelelteti ezeket a követelményeket.
Tartalomérvényesítés
Harmadik féltől származó vírus-/kártevőkeresési API-t használjon a feltöltött tartalmakon.
A fájlok beolvasása nagy mennyiségben igényel kiszolgálói erőforrásokat. Ha a kérésfeldolgozási teljesítmény a fájlvizsgálat miatt csökken, fontolja meg a beolvasási munka kiszervezését egy háttérszolgáltatásba, esetleg egy olyan szolgáltatásba, amely az alkalmazás kiszolgálójától eltérő kiszolgálón fut. A feltöltött fájlok általában karanténba helyezett területen vannak tárolva, amíg a háttér vírusolvasó nem ellenőrzi őket. Amikor egy fájl áthalad, a rendszer áthelyezi a fájlt a normál fájltárolási helyre. Ezeket a lépéseket általában egy olyan adatbázisrekorddal együtt hajtják végre, amely egy fájl vizsgálati állapotát jelzi. Egy ilyen megközelítés használatával az alkalmazás és az alkalmazáskiszolgáló továbbra is a kérelmek megválaszolására összpontosít.
Fájlkiterjesztés érvényesítése
A feltöltött fájl bővítményét ellenőrizni kell az engedélyezett bővítmények listájában. Például:
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
Fájl aláírásának ellenőrzése
A fájl aláírását a fájl elején található első néhány bájt határozza meg. Ezekkel a bájtokkal jelezhető, hogy a bővítmény megfelel-e a fájl tartalmának. A mintaalkalmazás néhány gyakori fájltípus esetében ellenőrzi a fájlaláírásokat. Az alábbi példában egy JPEG-kép fájlszignatúráját ellenőrzik a fájlhoz képest.
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
További fájl aláírások beszerzéséhez használjon fájl aláírási adatbázist (Google keresési eredmény) és a hivatalos fájlleírásokat. A hivatalos fájlspecifikációkkal való konzultációval biztosítható, hogy a kiválasztott aláírások érvényesek legyenek.
Fájlnév biztonsága
Soha ne használjon ügyfél által megadott fájlnevet a fájlok fizikai tárolóba való mentéséhez. Hozzon létre egy biztonságos fájlnevet a fájlhoz a Path.GetRandomFileName vagy a Path.GetTempFileName használatával, hogy létrehozhasson egy teljes elérési utat (beleértve a fájlnevet is) az ideiglenes tároláshoz.
Razor automatikusan HTML kódolja a tulajdonságértékeket a megjelenítéshez. A következő kód biztonságosan használható:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
A felhasználó kérésének Razorkivételével mindig HtmlEncode használd a fájlnév tartalmát.
Számos implementációnak tartalmaznia kell a fájl meglétének ellenőrzését; ellenkező esetben a fájlt felülírja egy azonos nevű fájl. Adjon meg további logikát az alkalmazás specifikációinak való megfeleléshez.
Méretellenőrzés
A feltöltött fájlok méretének korlátozása.
A mintaalkalmazásban a fájl mérete legfeljebb 2 MB lehet (bájtban jelölve). A korlátot a konfiguráción keresztül adja meg a appsettings.json fájlból:
{
"FileSizeLimit": 2097152
}
A FileSizeLimit be van szúrva a PageModel osztályokba.
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Ha egy fájlméret túllépi a korlátot, a rendszer elutasítja a fájlt:
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
A névattribútum értékének egyeztetése a POST metódus paraméter nevével
Olyan nem-Razor formátumú űrlapokon, amelyek POST formátumú adatokat küldenek, vagy közvetlenül JavaScript FormData használatát alkalmazzák, az űrlap elemében FormData megadott névnek meg kell egyeznie a vezérlő művelet paraméterének nevével.
Az alábbi példában:
Ha
<input>elemet használunk, akkor aznameattribútum értéke a következő lesz:battlePlans<input type="file" name="battlePlans" multiple>JavaScript használata
FormDataesetén a név a következő értékrebattlePlansvan állítva:var formData = new FormData(); for (var file in files) { formData.append("battlePlans", file, file.name); }
Adjon meg egyező nevet a C# metódus paraméterének (battlePlans):
Egy Razor oldalkezelési metódus neve
Upload:public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)MVC POST vezérlőműveleti módszer esetén:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
Kiszolgáló- és alkalmazáskonfiguráció
Többrészes törzs hosszának korlátja
MultipartBodyLengthLimit az egyes többrészes törzsek hosszára vonatkozó korlátot határozza meg. Az ezen korlátot meghaladó űrlapszakaszok elemzéskor adnak InvalidDataException értéket. Az alapértelmezett érték 134 217 728 (128 MB). Testreszabhatja a korlátot a MultipartBodyLengthLimit beállítás segítségével a Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute egy oldal vagy művelet MultipartBodyLengthLimit beállítására szolgál.
A Pages alkalmazásban alkalmazza a szűrőt a Razor a következőképpen :
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Razor A Pages- vagy MVC-alkalmazásokban alkalmazza a szűrőt az oldalmodellre vagy a műveletmetódusra:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Kestrel kérelem törzsének maximális mérete
Az üzemeltetett Kestrelalkalmazások esetében a kérelem törzsének alapértelmezett maximális mérete 30 000 000 bájt, ami körülbelül 28,6 MB. Szabja testre a korlátot a MaxRequestBodySizeKestrel kiszolgálói beállítással:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});
RequestSizeLimitAttribute a MaxRequestBodySize beállítása egyetlen lapra vagy műveletre.
A Pages alkalmazásban alkalmazza a szűrőt a Razor a következőképpen :
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Razor Lapalkalmazásban vagy MVC-alkalmazásban alkalmazza a szűrőt az oldalkezelő osztályra vagy műveletmetódusra:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Az RequestSizeLimitAttribute irányelvvel @attributeRazor is alkalmazhatók:
@attribute [RequestSizeLimitAttribute(52428800)]
Egyéb Kestrel korlátok
Egyéb Kestrel korlátozások vonatkozhatnak a következő által üzemeltetett Kestrelalkalmazásokra:
IIS
Az alapértelmezett kérelemkorlát (maxAllowedContentLength) 30 000 000 bájt, ami körülbelül 28,6 MB. Állítsa be a korlátot a web.config fájlban. Az alábbi példában a korlát értéke 50 MB (52 428 800 bájt):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength beállítás csak az IIS-ra vonatkozik. További információ: Kérelemkorlátok <requestLimits>.
Troubleshoot
Az alábbiakban néhány gyakori probléma merült fel a fájlok feltöltése és lehetséges megoldásai során.
Nem található hiba az IIS-kiszolgálón való üzembe helyezéskor
A következő hiba azt jelzi, hogy a feltöltött fájl túllépi a kiszolgáló konfigurált tartalomhosszát:
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
További információ: IIS szakasz.
Kapcsolódási hiba
A kapcsolati hiba és a kiszolgáló alaphelyzetbe állítása valószínűleg azt jelzi, hogy a feltöltött fájl meghaladja Kestrela kérelem törzsének maximális méretét. További információkért tekintse meg a Kestrel kérelem törzsének maximális méretét ismertető szakaszt. Kestrel az ügyfélkapcsolat korlátainak módosítása is szükséges lehet.
Null hivatkozási kivétel az IFormFile-nal
Ha a vezérlő a feltöltött fájlokat IFormFile elfogadja, de az érték null, ellenőrizze, hogy a HTML-űrlapon az enctype értéke multipart/form-data-e. Ha ez az attribútum nincs beállítva az <form> elemen, a fájlfeltöltés nem történik meg, és a kötött IFormFile argumentumok nem érvényesek null. Győződjön meg arról is, hogy a feltöltés elnevezése űrlapadatokban megegyezik az alkalmazás elnevezésével.
A stream túl hosszú volt
A jelen témakör példái a feltöltött fájl tartalmának tárolására támaszkodnak MemoryStream . A MemoryStream maximális mérete int.MaxValue. Ha az alkalmazás fájlfeltöltési forgatókönyve 50 MB-nál nagyobb fájltartalmat igényel, használjon olyan alternatív módszert, amely nem támaszkodik egyetlen MemoryStream feltöltött fájl tartalmának tárolására.
ASP.NET Core támogatja egy vagy több fájl feltöltését pufferelt modellkötés használatával a kisebb fájlokhoz, valamint a nagyobb fájlok nem kazettás streamelését.
Mintakód megtekintése vagy letöltése (hogyan töltsd le)
Biztonsági szempontok
Legyen óvatos, amikor engedélyezik a felhasználóknak a fájlok feltöltését a kiszolgálóra. A kiberbűnözők megpróbálhatják:
- Szolgáltatásmegtagadási támadások végrehajtása.
- Töltsön fel vírusokat vagy kártevőket.
- A hálózatokat és a kiszolgálókat más módon is veszélyeztetheti.
A sikeres támadás valószínűségét csökkentő biztonsági lépések a következők:
- Fájlok feltöltése dedikált fájlfeltöltési területre, lehetőleg nem rendszermeghajtóra. A dedikált helyek megkönnyítik a feltöltött fájlok biztonsági korlátozását. A fájlfeltöltési hely végrehajtási engedélyeinek letiltása.†
- Ne őrizze meg a feltöltött fájlokat ugyanabban a könyvtárfában, mint az app.†
- Használjon az alkalmazás által meghatározott biztonságos fájlnevet. Ne használjon a felhasználó által megadott fájlnevet vagy a feltöltött fájl nem megbízható fájlnevét.† a HTML kódolja a nem megbízható fájlnevet a megjelenítéskor. Például naplózhatja a fájl nevét, vagy megjelenítheti a felhasználói felületen (Razor a html automatikusan kódolja a kimenetet).
- Csak jóváhagyott fájlkiterjesztések engedélyezése az alkalmazás tervezési specifikációihoz.†
- Ellenőrizze, hogy az ügyféloldali ellenőrzések a szerveren történnek-e. Az ügyféloldali ellenőrzések könnyen megkerülhetők.
- Ellenőrizze a feltöltött fájl méretét. Állítson be maximális méretkorlátot a nagyméretű feltöltések elkerülése érdekében.
- Ha egy feltöltött fájl nem írhatja felül a fájlokat ugyanazzal a névvel, a fájl feltöltése előtt ellenőrizze a fájl nevét az adatbázison vagy a fizikai tárolón.
- A fájl tárolása előtt futtasson vírus-/kártevőolvasót a feltöltött tartalomon.
†A mintaalkalmazás egy olyan megközelítést mutat be, amely megfelel a feltételeknek.
Warning
A rosszindulatú kódok rendszerbe való feltöltése gyakran az első lépés a kód végrehajtásához, amely a következőre képes:
- Teljesen átveheti a rendszer irányítását.
- A rendszer túlterhelése azzal az eredménnyel, hogy a rendszer összeomlik.
- Felhasználói vagy rendszeradatok megsértése.
- Graffiti alkalmazása nyilvános felhasználói felületre.
A felhasználóktól érkező fájlok elfogadásakor fellépő biztonsági rések csökkentéséről az alábbi forrásokból tájékozódhat:
A biztonsági intézkedések implementálásával kapcsolatos további információkért, beleértve a mintaalkalmazásból származó példákat, tekintse meg az Ellenőrzés szakaszt.
Tárolási forgatókönyvek
A fájlok tárolási lehetőségei a következők:
Database
- Kis méretű fájlfeltöltések esetén az adatbázisok gyakran gyorsabbak, mint a fizikai tárolás (fájlrendszer vagy hálózati megosztás) beállításai.
- Az adatbázisok gyakran kényelmesebbek, mint a fizikai tárolási lehetőségek, mivel a felhasználói adatok adatbázisrekordjának lekérése egyidejűleg megadhatja a fájl tartalmát (például avatarképet).
- Az adatbázisok potenciálisan olcsóbbak, mint egy adattárolási szolgáltatás használata.
Fizikai tárolás (fájlrendszer vagy hálózati megosztás)
- Nagyméretű fájlfeltöltések esetén:
- Az adatbázis korlátai korlátozhatják a feltöltés méretét.
- A fizikai tárolás gyakran kevésbé gazdaságos, mint egy adatbázisban való tárolás.
- A fizikai tárolás potenciálisan olcsóbb, mint egy adattárolási szolgáltatás használata.
- Az alkalmazás folyamatának olvasási és írási engedélyekkel kell rendelkeznie a tárolási helyre. Soha ne adjon végrehajtási engedélyt.
- Nagyméretű fájlfeltöltések esetén:
Adattárolási szolgáltatás (például Azure Blob Storage)
- A szolgáltatások általában jobb méretezhetőséget és rugalmasságot biztosítanak a helyszíni megoldásokkal szemben, amelyek általában egyetlen meghibásodási pontnak vannak kitéve.
- A szolgáltatások költségei potenciálisan alacsonyabbak lehetnek nagy tárolási infrastruktúra esetében.
További információ : Rövid útmutató: Blob létrehozása az objektumtárolóban a .NET használatával.
Fájlfeltöltési forgatókönyvek
A fájlok feltöltésének két általános módszere a pufferelés és a streamelés.
Buffering
A rendszer a teljes fájlt beolvassa a IFormFilefájl feldolgozásához vagy mentéséhez használt fájl C#-reprezentációjába.
A fájlfeltöltések által használt erőforrások (lemez, memória) az egyidejű fájlfeltöltések számától és méretétől függenek. Ha egy alkalmazás túl sok feltöltést próbál pufferelni, a webhely összeomlik, amikor elfogy a memória vagy a lemezterület. Ha a fájlfeltöltések mérete vagy gyakorisága kimeríti az alkalmazás erőforrásait, használjon streamelést.
Note
A rendszer minden 64 KB-nál nagyobb pufferelt fájlt áthelyez a memóriából egy lemezen lévő ideiglenes fájlba.
A kis fájlok pufferelését a témakör alábbi szakaszai ismertetik:
Streaming
A fájl többrészes kérelemből érkezik, és az alkalmazás közvetlenül dolgozza fel vagy menti. A streamelés nem javítja jelentősen a teljesítményt. A streamelés csökkenti a memória vagy a lemezterület iránti igényeket a fájlok feltöltésekor.
A nagy fájlok streamelése a Nagy fájlok feltöltése streamelési szakaszában található.
Kis méretű fájlok feltöltése pufferelt modellkötéssel a fizikai tárolóba
Kis fájlok feltöltéséhez használjon többrészes űrlapot, vagy hozzon létre egy POST-kérést JavaScript használatával.
Az alábbi példa bemutatja, hogy egy Razor Pages-űrlapot használ-e egyetlen fájl feltöltésére (Pages/BufferedSingleFileUploadPhysical.cshtml a mintaalkalmazásban):
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
Az alábbi példa az előző példához hasonló, azzal a kivételével, hogy:
- A JavaScript (Fetch API) az űrlap adatainak elküldésére szolgál.
- Nincs ellenőrzés.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
A POST űrlap JavaScriptben való végrehajtásához olyan ügyfelek esetében, amelyek nem támogatják a Fetch API-t, használja az alábbi módszerek egyikét:
Használjon beolvasási polifillt (például window.fetch polyfill (github/fetch)).
Használja a
XMLHttpRequest. Például:<script> "use strict"; function AJAXSubmit (oFormElement) { var oReq = new XMLHttpRequest(); oReq.onload = function(e) { oFormElement.elements.namedItem("result").value = 'Result: ' + this.status + ' ' + this.statusText; }; oReq.open("post", oFormElement.action); oReq.send(new FormData(oFormElement)); } </script>
A fájlfeltöltések támogatásához a HTML-űrlapoknak meg kell adnia a kódolási típust (enctype) multipart/form-data.
Ha egy files bemeneti elem támogatja a több fájl feltöltését, adja meg az multiple elem attribútumát <input> :
<input asp-for="FileUpload.FormFiles" type="file" multiple>
A kiszolgálóra feltöltött egyes fájlok a Modellkötés funkcióval IFormFileérhetők el. A mintaalkalmazás több pufferelt fájlfeltöltést mutat be adatbázis- és fizikai tárolási forgatókönyvekhez.
Warning
Ne használja a FileName tulajdonságot másra, mint megjelenítésre és naplózásra. Megjelenítéskor vagy naplózáskor a HTML kódolja a fájlnevet. A kiberbűnözők rosszindulatú fájlnevet adhatnak meg, beleértve a teljes elérési utakat vagy a relatív elérési utakat. Az alkalmazásoknak a következőnek kell lennie:
- Távolítsa el az elérési utat a felhasználó által megadott fájlnévből.
- Mentse a HTML-kódolású, elérési út által eltávolított fájlnevet a felhasználói felülethez vagy a naplózáshoz.
- Hozzon létre egy új véletlenszerű fájlnevet a tárolóhoz.
A következő kód eltávolítja az elérési utat a fájlnévből:
string untrustedFileName = Path.GetFileName(pathName);
Az eddig megadott példák nem veszik figyelembe a biztonsági szempontokat. További információt a következő szakaszok és a mintaalkalmazás nyújt:
Amikor modellkötéssel tölt fel fájlokat, a IFormFileműveletmetódus a következőt tudja elfogadni:
- Egyetlen IFormFile.
- A következő gyűjtemények bármelyike, amelyek több fájlt jelölnek:
Note
Az összekapcsolás név szerint hozzárendeli az űrlapfájlokat. A HTML name értéknek a <input type="file" name="formFile"> elemen meg kell egyeznie a C# paraméterhez/tulajdonsághoz kötött (FormFile) értékkel. Lásd a POST metódus paraméternevének hozzárendelése az attribútumértékhez című szakaszt további információért.
A következő példa:
- Ismétlődően végigmegy egy vagy több feltöltött fájlon.
- A Path.GetTempFileName használatával egy fájl teljes elérési útját adja vissza, beleértve a fájl nevét is.
- Az alkalmazás által létrehozott fájlnév használatával menti a fájlokat a helyi fájlrendszerbe.
- A feltöltött fájlok teljes számát és méretét adja vissza.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Elérési út nélküli fájlnév létrehozásához használható Path.GetRandomFileName . Az alábbi példában az elérési út a konfigurációból származik:
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
A megadott FileStream elérési útnak tartalmaznia kell a fájlnevet. Ha a fájlnév nincs megadva, a rendszer futásidőben dob egy UnauthorizedAccessException nevet.
A technikával feltöltött fájlokat a IFormFile rendszer a feldolgozás előtt puffereli a memóriába vagy a kiszolgálón lévő lemezre. A műveletmetóduson belül a IFormFile tartalom elérhető, mint egy Stream. A helyi fájlrendszeren kívül fájlokat is menthet egy hálózati megosztásba vagy egy fájltároló szolgáltatásba, például az Azure Blob Storage-ba.
Egy másik példa, amely több fájlt hurkol a feltöltéshez, és biztonságos fájlneveket használ, lásd Pages/BufferedMultipleFileUploadPhysical.cshtml.cs a mintaalkalmazásban.
Warning
A Path.GetTempFileName kivételt IOException dob, ha több mint 65 535 fájlt hozunk létre az előző ideiglenes fájlok törlése nélkül. A 65 535 fájl maximális száma kiszolgálónkénti korlát. A Windows operációs rendszerre vonatkozó korlátról a következő témakörökben talál további információt:
Kis méretű fájlok feltöltése pufferelt modellkötéssel egy adatbázisba
Ha bináris fájladatokat szeretne tárolni egy adatbázisban az Entity Framework használatával, definiáljon egy Byte tömbtulajdonságot az entitáson:
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Adja meg a következőt tartalmazó osztály lapmodell-tulajdonságát IFormFile:
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Note
IFormFile közvetlenül műveletmetódus-paraméterként vagy kötött modelltulajdonságként használható. Az előző példa egy kötött modelltulajdonságot használ.
A FileUpload a Razor Lapok űrlapban használatos.
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Amikor az űrlap küldve van a kiszolgálóra, másolja a IFormFile egy streambe, és mentse el egy bájttömbként az adatbázisban. Az alábbi példában _dbContext az alkalmazás adatbázis-környezetét tárolja:
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
Az előző példa hasonló a mintaalkalmazásban bemutatott forgatókönyvhöz:
Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml.cs
Warning
A bináris adatok relációs adatbázisokban való tárolásakor körültekintően kell eljárni, mert az hátrányosan befolyásolhatja a teljesítményt.
Ne támaszkodjon és ne bízzon a FileName tulajdonságban a IFormFile esetében ellenőrzés nélkül. A FileName tulajdonság csak megjelenítési célokra és csak HTML-kódolás után használható.
A megadott példák nem veszik figyelembe a biztonsági szempontokat. További információt a következő szakaszok és a mintaalkalmazás nyújt:
Nagyméretű fájlok feltöltése streameléssel
Az alábbi példa bemutatja, hogyan streamelhet egy fájlt egy vezérlőműveletbe JavaScript használatával. A fájl antiforgery jogkivonata egy egyedi szűrőattribútum használatával jön létre, és az ügyfél HTTP-fejléceibe kerül továbbításra a kérelem törzse helyett. Mivel a műveletmetódus közvetlenül dolgozza fel a feltöltött adatokat, az űrlapmodell kötését egy másik egyéni szűrő letiltja. A műveleten belül az űrlap tartalma egy MultipartReader segítségével olvasható be, amely minden egyes MultipartSection-et feldolgoz, vagy a tartalmat megfelelő módon tárolja. A többrészes szakaszok beolvasása után a művelet végrehajtja a saját modellkötését.
A kezdeti lap válasza betölti az űrlapot, és ment egy antiforgery tokent ( cookie az GenerateAntiforgeryTokenCookieAttribute attribútumon keresztül). Az attribútum az ASP.NET Core beépített antiforgery-támogatásával állít be egy cookie kérési token-t.
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
A DisableFormValueModelBindingAttribute modellkötés letiltására szolgál:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
A mintaalkalmazásban a GenerateAntiforgeryTokenCookieAttribute és a DisableFormValueModelBindingAttribute szűrőkként vannak alkalmazva a /StreamedSingleFileUploadDb oldalalkalmazási modelleken, mint például a /StreamedSingleFileUploadPhysical és a Startup.ConfigureServices, a Razor Pages-konvenciók használatával.
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});
Mivel a modellkötés nem olvassa be az űrlapot, az űrlapról kötött paraméterek nem kötnek (a lekérdezés, az útvonal és az élőfej továbbra is működik). A műveletmetódus közvetlenül a Request tulajdonsággal működik. Az A MultipartReader az egyes szakaszok olvasására szolgál. Kulcs-/értékadatok egy KeyValueAccumulator-ben vannak tárolva a rendszerben. A többrészes szakaszok beolvasása után a rendszer az KeyValueAccumulator űrlapadatokat modelltípushoz köti.
Az adatbázisba való streamelés teljes StreamingController.UploadDatabase módszere a következővel EF Core:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
A fizikai helyre történő streamelés teljes StreamingController.UploadPhysical módja:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
A mintaalkalmazásban az érvényesítési ellenőrzéseket a FileHelpers.ProcessStreamedFilerendszer kezeli.
Validation
A mintaalkalmazás osztálya FileHelpers számos ellenőrzést mutat be a pufferelt IFormFile és streamelt fájlfeltöltések esetében. A pufferelt fájlfeltöltések a mintaalkalmazásban való feldolgozásához IFormFile tekintse meg a ProcessFormFile fájl metódusát Utilities/FileHelpers.cs . A streamelt fájlok feldolgozásához tekintse meg a ProcessStreamedFile metódust ugyanabban a fájlban.
Warning
A mintaalkalmazásban bemutatott érvényesítési feldolgozási módszerek nem ellenőrzik a feltöltött fájlok tartalmát. A legtöbb éles forgatókönyvben egy vírus-/kártevőolvasó API-t használnak a fájlon, mielőtt elérhetővé tennék a fájlt a felhasználók vagy más rendszerek számára.
Bár a témakörminta az érvényesítési technikákat szemlélteti, ne implementálja az FileHelpers osztályt egy éles alkalmazásban, kivéve, ha:
- A megvalósítás teljes körű megértése.
- Módosítsa a megvalósítást az alkalmazás környezetének és specifikációinak megfelelően.
Soha ne implementáljon megkülönböztetés nélkül biztonsági kódot egy alkalmazásban anélkül, hogy megfelelteti ezeket a követelményeket.
Tartalomérvényesítés
Harmadik féltől származó vírus-/kártevőkeresési API-t használjon a feltöltött tartalmakon.
A fájlok beolvasása nagy mennyiségben igényel kiszolgálói erőforrásokat. Ha a kérésfeldolgozási teljesítmény a fájlvizsgálat miatt csökken, fontolja meg a beolvasási munka kiszervezését egy háttérszolgáltatásba, esetleg egy olyan szolgáltatásba, amely az alkalmazás kiszolgálójától eltérő kiszolgálón fut. A feltöltött fájlok általában karanténba helyezett területen vannak tárolva, amíg a háttér vírusolvasó nem ellenőrzi őket. Amikor egy fájl áthalad, a rendszer áthelyezi a fájlt a normál fájltárolási helyre. Ezeket a lépéseket általában egy olyan adatbázisrekorddal együtt hajtják végre, amely egy fájl vizsgálati állapotát jelzi. Egy ilyen megközelítés használatával az alkalmazás és az alkalmazáskiszolgáló továbbra is a kérelmek megválaszolására összpontosít.
Fájlkiterjesztés érvényesítése
A feltöltött fájl bővítményét ellenőrizni kell az engedélyezett bővítmények listájában. Például:
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
Fájl aláírásának ellenőrzése
A fájl aláírását a fájl elején található első néhány bájt határozza meg. Ezekkel a bájtokkal jelezhető, hogy a bővítmény megfelel-e a fájl tartalmának. A mintaalkalmazás néhány gyakori fájltípus esetében ellenőrzi a fájlaláírásokat. Az alábbi példában egy JPEG-kép fájlszignatúráját ellenőrzik a fájlhoz képest.
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
További fájl aláírások beszerzéséhez használjon fájl aláírási adatbázist (Google keresési eredmény) és a hivatalos fájlleírásokat. A hivatalos fájlspecifikációkkal való konzultációval biztosítható, hogy a kiválasztott aláírások érvényesek legyenek.
Fájlnév biztonsága
Soha ne használjon ügyfél által megadott fájlnevet a fájlok fizikai tárolóba való mentéséhez. Hozzon létre egy biztonságos fájlnevet a fájlhoz a Path.GetRandomFileName vagy a Path.GetTempFileName használatával, hogy létrehozhasson egy teljes elérési utat (beleértve a fájlnevet is) az ideiglenes tároláshoz.
Razor automatikusan HTML kódolja a tulajdonságértékeket a megjelenítéshez. A következő kód biztonságosan használható:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
A felhasználó kérésének Razorkivételével mindig HtmlEncode használd a fájlnév tartalmát.
Számos implementációnak tartalmaznia kell a fájl meglétének ellenőrzését; ellenkező esetben a fájlt felülírja egy azonos nevű fájl. Adjon meg további logikát az alkalmazás specifikációinak való megfeleléshez.
Méretellenőrzés
A feltöltött fájlok méretének korlátozása.
A mintaalkalmazásban a fájl mérete legfeljebb 2 MB lehet (bájtban jelölve). A korlátot a konfiguráción keresztül adja meg a appsettings.json fájlból:
{
"FileSizeLimit": 2097152
}
A FileSizeLimit be van szúrva a PageModel osztályokba.
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Ha egy fájlméret túllépi a korlátot, a rendszer elutasítja a fájlt:
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
A névattribútum értékének egyeztetése a POST metódus paraméter nevével
Olyan nem-Razor formátumú űrlapokon, amelyek POST formátumú adatokat küldenek, vagy közvetlenül JavaScript FormData használatát alkalmazzák, az űrlap elemében FormData megadott névnek meg kell egyeznie a vezérlő művelet paraméterének nevével.
Az alábbi példában:
Ha
<input>elemet használunk, akkor aznameattribútum értéke a következő lesz:battlePlans<input type="file" name="battlePlans" multiple>JavaScript használata
FormDataesetén a név a következő értékrebattlePlansvan állítva:var formData = new FormData(); for (var file in files) { formData.append("battlePlans", file, file.name); }
Adjon meg egyező nevet a C# metódus paraméterének (battlePlans):
Egy Razor oldalkezelési metódus neve
Upload:public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)MVC POST vezérlőműveleti módszer esetén:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
Kiszolgáló- és alkalmazáskonfiguráció
Többrészes törzs hosszának korlátja
MultipartBodyLengthLimit az egyes többrészes törzsek hosszára vonatkozó korlátot határozza meg. Az ezen korlátot meghaladó űrlapszakaszok elemzéskor adnak InvalidDataException értéket. Az alapértelmezett érték 134 217 728 (128 MB). Testreszabhatja a korlátot a MultipartBodyLengthLimit beállítás segítségével a Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute egy oldal vagy művelet MultipartBodyLengthLimit beállítására szolgál.
A Pages alkalmazásban alkalmazza a szűrőt a Razor a következőképpen :
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Razor A Pages- vagy MVC-alkalmazásokban alkalmazza a szűrőt az oldalmodellre vagy a műveletmetódusra:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Kestrel kérelem törzsének maximális mérete
Az üzemeltetett Kestrelalkalmazások esetében a kérelem törzsének alapértelmezett maximális mérete 30 000 000 bájt, ami körülbelül 28,6 MB. Szabja testre a korlátot a MaxRequestBodySizeKestrel kiszolgálói beállítással:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});
RequestSizeLimitAttribute a MaxRequestBodySize beállítása egyetlen lapra vagy műveletre.
A Pages alkalmazásban alkalmazza a szűrőt a Razor a következőképpen :
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Razor Lapalkalmazásban vagy MVC-alkalmazásban alkalmazza a szűrőt az oldalkezelő osztályra vagy műveletmetódusra:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Az RequestSizeLimitAttribute irányelvvel @attributeRazor is alkalmazhatók:
@attribute [RequestSizeLimitAttribute(52428800)]
Egyéb Kestrel korlátok
Egyéb Kestrel korlátozások vonatkozhatnak a következő által üzemeltetett Kestrelalkalmazásokra:
IIS
Az alapértelmezett kérelemkorlát (maxAllowedContentLength) 30 000 000 bájt, ami körülbelül 28,6 MB. Állítsa be a korlátot a web.config fájlban. Az alábbi példában a korlát értéke 50 MB (52 428 800 bájt):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength beállítás csak az IIS-ra vonatkozik. További információ: Kérelemkorlátok <requestLimits>.
A HTTP-kérés maximális kérelemtörzsméretének növelése a következő beállítással IISServerOptions.MaxRequestBodySizeStartup.ConfigureServices: . Az alábbi példában a korlát értéke 50 MB (52 428 800 bájt):
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 52428800;
});
További információ: Host ASP.NET Core on Windows with IIS.
Troubleshoot
Az alábbiakban néhány gyakori probléma merült fel a fájlok feltöltése és lehetséges megoldásai során.
Nem található hiba az IIS-kiszolgálón való üzembe helyezéskor
A következő hiba azt jelzi, hogy a feltöltött fájl túllépi a kiszolgáló konfigurált tartalomhosszát:
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
További információ: IIS szakasz.
Kapcsolódási hiba
A kapcsolati hiba és a kiszolgáló alaphelyzetbe állítása valószínűleg azt jelzi, hogy a feltöltött fájl meghaladja Kestrela kérelem törzsének maximális méretét. További információkért tekintse meg a Kestrel kérelem törzsének maximális méretét ismertető szakaszt. Kestrel az ügyfélkapcsolat korlátainak módosítása is szükséges lehet.
Null hivatkozási kivétel az IFormFile-nal
Ha a vezérlő a feltöltött fájlokat IFormFile elfogadja, de az érték null, ellenőrizze, hogy a HTML-űrlapon az enctype értéke multipart/form-data-e. Ha ez az attribútum nincs beállítva az <form> elemen, a fájlfeltöltés nem történik meg, és a kötött IFormFile argumentumok nem érvényesek null. Győződjön meg arról is, hogy a feltöltés elnevezése űrlapadatokban megegyezik az alkalmazás elnevezésével.
A stream túl hosszú volt
A jelen témakör példái a feltöltött fájl tartalmának tárolására támaszkodnak MemoryStream . A MemoryStream maximális mérete int.MaxValue. Ha az alkalmazás fájlfeltöltési forgatókönyve 50 MB-nál nagyobb fájltartalmat igényel, használjon olyan alternatív módszert, amely nem támaszkodik egyetlen MemoryStream feltöltött fájl tartalmának tárolására.
ASP.NET Core támogatja egy vagy több fájl feltöltését pufferelt modellkötés használatával a kisebb fájlokhoz, valamint a nagyobb fájlok nem kazettás streamelését.
Mintakód megtekintése vagy letöltése (hogyan töltsd le)
Biztonsági szempontok
Legyen óvatos, amikor engedélyezik a felhasználóknak a fájlok feltöltését a kiszolgálóra. A kiberbűnözők megpróbálhatják:
- Szolgáltatásmegtagadási támadások végrehajtása.
- Töltsön fel vírusokat vagy kártevőket.
- A hálózatokat és a kiszolgálókat más módon is veszélyeztetheti.
A sikeres támadás valószínűségét csökkentő biztonsági lépések a következők:
- Fájlok feltöltése dedikált fájlfeltöltési területre, lehetőleg nem rendszermeghajtóra. A dedikált helyek megkönnyítik a feltöltött fájlok biztonsági korlátozását. A fájlfeltöltési hely végrehajtási engedélyeinek letiltása.†
- Ne őrizze meg a feltöltött fájlokat ugyanabban a könyvtárfában, mint az app.†
- Használjon az alkalmazás által meghatározott biztonságos fájlnevet. Ne használjon a felhasználó által megadott fájlnevet vagy a feltöltött fájl nem megbízható fájlnevét.† a HTML kódolja a nem megbízható fájlnevet a megjelenítéskor. Például naplózhatja a fájl nevét, vagy megjelenítheti a felhasználói felületen (Razor a html automatikusan kódolja a kimenetet).
- Csak jóváhagyott fájlkiterjesztések engedélyezése az alkalmazás tervezési specifikációihoz.†
- Ellenőrizze, hogy az ügyféloldali ellenőrzések a szerveren történnek-e. Az ügyféloldali ellenőrzések könnyen megkerülhetők.
- Ellenőrizze a feltöltött fájl méretét. Állítson be maximális méretkorlátot a nagyméretű feltöltések elkerülése érdekében.
- Ha egy feltöltött fájl nem írhatja felül a fájlokat ugyanazzal a névvel, a fájl feltöltése előtt ellenőrizze a fájl nevét az adatbázison vagy a fizikai tárolón.
- A fájl tárolása előtt futtasson vírus-/kártevőolvasót a feltöltött tartalomon.
†A mintaalkalmazás egy olyan megközelítést mutat be, amely megfelel a feltételeknek.
Warning
A rosszindulatú kódok rendszerbe való feltöltése gyakran az első lépés a kód végrehajtásához, amely a következőre képes:
- Teljesen átveheti a rendszer irányítását.
- A rendszer túlterhelése azzal az eredménnyel, hogy a rendszer összeomlik.
- Felhasználói vagy rendszeradatok megsértése.
- Graffiti alkalmazása nyilvános felhasználói felületre.
A felhasználóktól érkező fájlok elfogadásakor fellépő biztonsági rések csökkentéséről az alábbi forrásokból tájékozódhat:
A biztonsági intézkedések implementálásával kapcsolatos további információkért, beleértve a mintaalkalmazásból származó példákat, tekintse meg az Ellenőrzés szakaszt.
Tárolási forgatókönyvek
A fájlok tárolási lehetőségei a következők:
Database
- Kis méretű fájlfeltöltések esetén az adatbázisok gyakran gyorsabbak, mint a fizikai tárolás (fájlrendszer vagy hálózati megosztás) beállításai.
- Az adatbázisok gyakran kényelmesebbek, mint a fizikai tárolási lehetőségek, mivel a felhasználói adatok adatbázisrekordjának lekérése egyidejűleg megadhatja a fájl tartalmát (például avatarképet).
- Az adatbázisok potenciálisan olcsóbbak, mint egy adattárolási szolgáltatás használata.
Fizikai tárolás (fájlrendszer vagy hálózati megosztás)
- Nagyméretű fájlfeltöltések esetén:
- Az adatbázis korlátai korlátozhatják a feltöltés méretét.
- A fizikai tárolás gyakran kevésbé gazdaságos, mint egy adatbázisban való tárolás.
- A fizikai tárolás potenciálisan olcsóbb, mint egy adattárolási szolgáltatás használata.
- Az alkalmazás folyamatának olvasási és írási engedélyekkel kell rendelkeznie a tárolási helyre. Soha ne adjon végrehajtási engedélyt.
- Nagyméretű fájlfeltöltések esetén:
Adattárolási szolgáltatás (például Azure Blob Storage)
- A szolgáltatások általában jobb méretezhetőséget és rugalmasságot biztosítanak a helyszíni megoldásokkal szemben, amelyek általában egyetlen meghibásodási pontnak vannak kitéve.
- A szolgáltatások költségei potenciálisan alacsonyabbak lehetnek nagy tárolási infrastruktúra esetében.
További információ : Rövid útmutató: Blob létrehozása az objektumtárolóban a .NET használatával. A témakör bemutatja
UploadFromFileAsync, viszont aUploadFromStreamAsynchasználható arra, hogy egy FileStream mentésre kerüljön a blobtárolóba, amikor egy Stream kerül alkalmazásra.
Fájlfeltöltési forgatókönyvek
A fájlok feltöltésének két általános módszere a pufferelés és a streamelés.
Buffering
A rendszer a teljes fájlt beolvassa a IFormFilefájl feldolgozásához vagy mentéséhez használt fájl C#-reprezentációjába.
A fájlfeltöltések által használt erőforrások (lemez, memória) az egyidejű fájlfeltöltések számától és méretétől függenek. Ha egy alkalmazás túl sok feltöltést próbál pufferelni, a webhely összeomlik, amikor elfogy a memória vagy a lemezterület. Ha a fájlfeltöltések mérete vagy gyakorisága kimeríti az alkalmazás erőforrásait, használjon streamelést.
Note
A rendszer minden 64 KB-nál nagyobb pufferelt fájlt áthelyez a memóriából egy lemezen lévő ideiglenes fájlba.
A kis fájlok pufferelését a témakör alábbi szakaszai ismertetik:
Streaming
A fájl többrészes kérelemből érkezik, és az alkalmazás közvetlenül dolgozza fel vagy menti. A streamelés nem javítja jelentősen a teljesítményt. A streamelés csökkenti a memória vagy a lemezterület iránti igényeket a fájlok feltöltésekor.
A nagy fájlok streamelése a Nagy fájlok feltöltése streamelési szakaszában található.
Kis méretű fájlok feltöltése pufferelt modellkötéssel a fizikai tárolóba
Kis fájlok feltöltéséhez használjon többrészes űrlapot, vagy hozzon létre egy POST-kérést JavaScript használatával.
Az alábbi példa bemutatja, hogy egy Razor Pages-űrlapot használ-e egyetlen fájl feltöltésére (Pages/BufferedSingleFileUploadPhysical.cshtml a mintaalkalmazásban):
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
Az alábbi példa az előző példához hasonló, azzal a kivételével, hogy:
- A JavaScript (Fetch API) az űrlap adatainak elküldésére szolgál.
- Nincs ellenőrzés.
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
A POST űrlap JavaScriptben való végrehajtásához olyan ügyfelek esetében, amelyek nem támogatják a Fetch API-t, használja az alábbi módszerek egyikét:
Használjon beolvasási polifillt (például window.fetch polyfill (github/fetch)).
Használja a
XMLHttpRequest. Például:<script> "use strict"; function AJAXSubmit (oFormElement) { var oReq = new XMLHttpRequest(); oReq.onload = function(e) { oFormElement.elements.namedItem("result").value = 'Result: ' + this.status + ' ' + this.statusText; }; oReq.open("post", oFormElement.action); oReq.send(new FormData(oFormElement)); } </script>
A fájlfeltöltések támogatásához a HTML-űrlapoknak meg kell adnia a kódolási típust (enctype) multipart/form-data.
Ha egy files bemeneti elem támogatja a több fájl feltöltését, adja meg az multiple elem attribútumát <input> :
<input asp-for="FileUpload.FormFiles" type="file" multiple>
A kiszolgálóra feltöltött egyes fájlok a Modellkötés funkcióval IFormFileérhetők el. A mintaalkalmazás több pufferelt fájlfeltöltést mutat be adatbázis- és fizikai tárolási forgatókönyvekhez.
Warning
Ne használja a FileName tulajdonságot másra, mint megjelenítésre és naplózásra. Megjelenítéskor vagy naplózáskor a HTML kódolja a fájlnevet. A kiberbűnözők rosszindulatú fájlnevet adhatnak meg, beleértve a teljes elérési utakat vagy a relatív elérési utakat. Az alkalmazásoknak a következőnek kell lennie:
- Távolítsa el az elérési utat a felhasználó által megadott fájlnévből.
- Mentse a HTML-kódolású, elérési út által eltávolított fájlnevet a felhasználói felülethez vagy a naplózáshoz.
- Hozzon létre egy új véletlenszerű fájlnevet a tárolóhoz.
A következő kód eltávolítja az elérési utat a fájlnévből:
string untrustedFileName = Path.GetFileName(pathName);
Az eddig megadott példák nem veszik figyelembe a biztonsági szempontokat. További információt a következő szakaszok és a mintaalkalmazás nyújt:
Amikor modellkötéssel tölt fel fájlokat, a IFormFileműveletmetódus a következőt tudja elfogadni:
- Egyetlen IFormFile.
- A következő gyűjtemények bármelyike, amelyek több fájlt jelölnek:
Note
Az összekapcsolás név szerint hozzárendeli az űrlapfájlokat. A HTML name értéknek a <input type="file" name="formFile"> elemen meg kell egyeznie a C# paraméterhez/tulajdonsághoz kötött (FormFile) értékkel. Lásd a POST metódus paraméternevének hozzárendelése az attribútumértékhez című szakaszt további információért.
A következő példa:
- Ismétlődően végigmegy egy vagy több feltöltött fájlon.
- A Path.GetTempFileName használatával egy fájl teljes elérési útját adja vissza, beleértve a fájl nevét is.
- Az alkalmazás által létrehozott fájlnév használatával menti a fájlokat a helyi fájlrendszerbe.
- A feltöltött fájlok teljes számát és méretét adja vissza.
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Elérési út nélküli fájlnév létrehozásához használható Path.GetRandomFileName . Az alábbi példában az elérési út a konfigurációból származik:
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
A megadott FileStream elérési útnak tartalmaznia kell a fájlnevet. Ha a fájlnév nincs megadva, a rendszer futásidőben dob egy UnauthorizedAccessException nevet.
A technikával feltöltött fájlokat a IFormFile rendszer a feldolgozás előtt puffereli a memóriába vagy a kiszolgálón lévő lemezre. A műveletmetóduson belül a IFormFile tartalom elérhető, mint egy Stream. A helyi fájlrendszeren kívül fájlokat is menthet egy hálózati megosztásba vagy egy fájltároló szolgáltatásba, például az Azure Blob Storage-ba.
Egy másik példa, amely több fájlt hurkol a feltöltéshez, és biztonságos fájlneveket használ, lásd Pages/BufferedMultipleFileUploadPhysical.cshtml.cs a mintaalkalmazásban.
Warning
A Path.GetTempFileName kivételt IOException dob, ha több mint 65 535 fájlt hozunk létre az előző ideiglenes fájlok törlése nélkül. A 65 535 fájl maximális száma kiszolgálónkénti korlát. A Windows operációs rendszerre vonatkozó korlátról a következő témakörökben talál további információt:
Kis méretű fájlok feltöltése pufferelt modellkötéssel egy adatbázisba
Ha bináris fájladatokat szeretne tárolni egy adatbázisban az Entity Framework használatával, definiáljon egy Byte tömbtulajdonságot az entitáson:
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Adja meg a következőt tartalmazó osztály lapmodell-tulajdonságát IFormFile:
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Note
IFormFile közvetlenül műveletmetódus-paraméterként vagy kötött modelltulajdonságként használható. Az előző példa egy kötött modelltulajdonságot használ.
A FileUpload a Razor Lapok űrlapban használatos.
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Amikor az űrlap küldve van a kiszolgálóra, másolja a IFormFile egy streambe, és mentse el egy bájttömbként az adatbázisban. Az alábbi példában _dbContext az alkalmazás adatbázis-környezetét tárolja:
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
Az előző példa hasonló a mintaalkalmazásban bemutatott forgatókönyvhöz:
Pages/BufferedSingleFileUploadDb.cshtmlPages/BufferedSingleFileUploadDb.cshtml.cs
Warning
A bináris adatok relációs adatbázisokban való tárolásakor körültekintően kell eljárni, mert az hátrányosan befolyásolhatja a teljesítményt.
Ne támaszkodjon és ne bízzon a FileName tulajdonságban a IFormFile esetében ellenőrzés nélkül. A FileName tulajdonság csak megjelenítési célokra és csak HTML-kódolás után használható.
A megadott példák nem veszik figyelembe a biztonsági szempontokat. További információt a következő szakaszok és a mintaalkalmazás nyújt:
Nagyméretű fájlok feltöltése streameléssel
Az alábbi példa bemutatja, hogyan streamelhet egy fájlt egy vezérlőműveletbe JavaScript használatával. A fájl antiforgery jogkivonata egy egyedi szűrőattribútum használatával jön létre, és az ügyfél HTTP-fejléceibe kerül továbbításra a kérelem törzse helyett. Mivel a műveletmetódus közvetlenül dolgozza fel a feltöltött adatokat, az űrlapmodell kötését egy másik egyéni szűrő letiltja. A műveleten belül az űrlap tartalma egy MultipartReader segítségével olvasható be, amely minden egyes MultipartSection-et feldolgoz, vagy a tartalmat megfelelő módon tárolja. A többrészes szakaszok beolvasása után a művelet végrehajtja a saját modellkötését.
A kezdeti lap válasza betölti az űrlapot, és ment egy antiforgery tokent ( cookie az GenerateAntiforgeryTokenCookieAttribute attribútumon keresztül). Az attribútum az ASP.NET Core beépített antiforgery-támogatásával állít be egy cookie kérési token-t.
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
A DisableFormValueModelBindingAttribute modellkötés letiltására szolgál:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
A mintaalkalmazásban a GenerateAntiforgeryTokenCookieAttribute és a DisableFormValueModelBindingAttribute szűrőkként vannak alkalmazva a /StreamedSingleFileUploadDb oldalalkalmazási modelleken, mint például a /StreamedSingleFileUploadPhysical és a Startup.ConfigureServices, a Razor Pages-konvenciók használatával.
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Mivel a modellkötés nem olvassa be az űrlapot, az űrlapról kötött paraméterek nem kötnek (a lekérdezés, az útvonal és az élőfej továbbra is működik). A műveletmetódus közvetlenül a Request tulajdonsággal működik. Az A MultipartReader az egyes szakaszok olvasására szolgál. Kulcs-/értékadatok egy KeyValueAccumulator-ben vannak tárolva a rendszerben. A többrészes szakaszok beolvasása után a rendszer az KeyValueAccumulator űrlapadatokat modelltípushoz köti.
Az adatbázisba való streamelés teljes StreamingController.UploadDatabase módszere a következővel EF Core:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper (Utilities/MultipartRequestHelper.cs):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
A fizikai helyre történő streamelés teljes StreamingController.UploadPhysical módja:
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
A mintaalkalmazásban az érvényesítési ellenőrzéseket a FileHelpers.ProcessStreamedFilerendszer kezeli.
Validation
A mintaalkalmazás osztálya FileHelpers számos ellenőrzést mutat be a pufferelt IFormFile és streamelt fájlfeltöltések esetében. A pufferelt fájlfeltöltések a mintaalkalmazásban való feldolgozásához IFormFile tekintse meg a ProcessFormFile fájl metódusát Utilities/FileHelpers.cs . A streamelt fájlok feldolgozásához tekintse meg a ProcessStreamedFile metódust ugyanabban a fájlban.
Warning
A mintaalkalmazásban bemutatott érvényesítési feldolgozási módszerek nem ellenőrzik a feltöltött fájlok tartalmát. A legtöbb éles forgatókönyvben egy vírus-/kártevőolvasó API-t használnak a fájlon, mielőtt elérhetővé tennék a fájlt a felhasználók vagy más rendszerek számára.
Bár a témakörminta az érvényesítési technikákat szemlélteti, ne implementálja az FileHelpers osztályt egy éles alkalmazásban, kivéve, ha:
- A megvalósítás teljes körű megértése.
- Módosítsa a megvalósítást az alkalmazás környezetének és specifikációinak megfelelően.
Soha ne implementáljon megkülönböztetés nélkül biztonsági kódot egy alkalmazásban anélkül, hogy megfelelteti ezeket a követelményeket.
Tartalomérvényesítés
Harmadik féltől származó vírus-/kártevőkeresési API-t használjon a feltöltött tartalmakon.
A fájlok beolvasása nagy mennyiségben igényel kiszolgálói erőforrásokat. Ha a kérésfeldolgozási teljesítmény a fájlvizsgálat miatt csökken, fontolja meg a beolvasási munka kiszervezését egy háttérszolgáltatásba, esetleg egy olyan szolgáltatásba, amely az alkalmazás kiszolgálójától eltérő kiszolgálón fut. A feltöltött fájlok általában karanténba helyezett területen vannak tárolva, amíg a háttér vírusolvasó nem ellenőrzi őket. Amikor egy fájl áthalad, a rendszer áthelyezi a fájlt a normál fájltárolási helyre. Ezeket a lépéseket általában egy olyan adatbázisrekorddal együtt hajtják végre, amely egy fájl vizsgálati állapotát jelzi. Egy ilyen megközelítés használatával az alkalmazás és az alkalmazáskiszolgáló továbbra is a kérelmek megválaszolására összpontosít.
Fájlkiterjesztés érvényesítése
A feltöltött fájl bővítményét ellenőrizni kell az engedélyezett bővítmények listájában. Például:
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
Fájl aláírásának ellenőrzése
A fájl aláírását a fájl elején található első néhány bájt határozza meg. Ezekkel a bájtokkal jelezhető, hogy a bővítmény megfelel-e a fájl tartalmának. A mintaalkalmazás néhány gyakori fájltípus esetében ellenőrzi a fájlaláírásokat. Az alábbi példában egy JPEG-kép fájlszignatúráját ellenőrzik a fájlhoz képest.
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
További fájl aláírások beszerzéséhez használjon fájl aláírási adatbázist (Google keresési eredmény) és a hivatalos fájlleírásokat. A hivatalos fájlspecifikációkkal való konzultációval biztosítható, hogy a kiválasztott aláírások érvényesek legyenek.
Fájlnév biztonsága
Soha ne használjon ügyfél által megadott fájlnevet a fájlok fizikai tárolóba való mentéséhez. Hozzon létre egy biztonságos fájlnevet a fájlhoz a Path.GetRandomFileName vagy a Path.GetTempFileName használatával, hogy létrehozhasson egy teljes elérési utat (beleértve a fájlnevet is) az ideiglenes tároláshoz.
Razor automatikusan HTML kódolja a tulajdonságértékeket a megjelenítéshez. A következő kód biztonságosan használható:
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
A felhasználó kérésének Razorkivételével mindig HtmlEncode használd a fájlnév tartalmát.
Számos implementációnak tartalmaznia kell a fájl meglétének ellenőrzését; ellenkező esetben a fájlt felülírja egy azonos nevű fájl. Adjon meg további logikát az alkalmazás specifikációinak való megfeleléshez.
Méretellenőrzés
A feltöltött fájlok méretének korlátozása.
A mintaalkalmazásban a fájl mérete legfeljebb 2 MB lehet (bájtban jelölve). A korlátot a konfiguráción keresztül adja meg a appsettings.json fájlból:
{
"FileSizeLimit": 2097152
}
A FileSizeLimit be van szúrva a PageModel osztályokba.
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Ha egy fájlméret túllépi a korlátot, a rendszer elutasítja a fájlt:
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
A névattribútum értékének egyeztetése a POST metódus paraméter nevével
Olyan nem-Razor formátumú űrlapokon, amelyek POST formátumú adatokat küldenek, vagy közvetlenül JavaScript FormData használatát alkalmazzák, az űrlap elemében FormData megadott névnek meg kell egyeznie a vezérlő művelet paraméterének nevével.
Az alábbi példában:
Ha
<input>elemet használunk, akkor aznameattribútum értéke a következő lesz:battlePlans<input type="file" name="battlePlans" multiple>JavaScript használata
FormDataesetén a név a következő értékrebattlePlansvan állítva:var formData = new FormData(); for (var file in files) { formData.append("battlePlans", file, file.name); }
Adjon meg egyező nevet a C# metódus paraméterének (battlePlans):
Egy Razor oldalkezelési metódus neve
Upload:public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)MVC POST vezérlőműveleti módszer esetén:
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
Kiszolgáló- és alkalmazáskonfiguráció
Többrészes törzs hosszának korlátja
MultipartBodyLengthLimit az egyes többrészes törzsek hosszára vonatkozó korlátot határozza meg. Az ezen korlátot meghaladó űrlapszakaszok elemzéskor adnak InvalidDataException értéket. Az alapértelmezett érték 134 217 728 (128 MB). Testreszabhatja a korlátot a MultipartBodyLengthLimit beállítás segítségével a Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute egy oldal vagy művelet MultipartBodyLengthLimit beállítására szolgál.
A Pages alkalmazásban alkalmazza a szűrőt a Razor a következőképpen :
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Razor A Pages- vagy MVC-alkalmazásokban alkalmazza a szűrőt az oldalmodellre vagy a műveletmetódusra:
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Kestrel kérelem törzsének maximális mérete
Az üzemeltetett Kestrelalkalmazások esetében a kérelem törzsének alapértelmezett maximális mérete 30 000 000 bájt, ami körülbelül 28,6 MB. Szabja testre a korlátot a MaxRequestBodySizeKestrel kiszolgálói beállítással:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
});
RequestSizeLimitAttribute a MaxRequestBodySize beállítása egyetlen lapra vagy műveletre.
A Pages alkalmazásban alkalmazza a szűrőt a Razor a következőképpen :
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Razor Lapalkalmazásban vagy MVC-alkalmazásban alkalmazza a szűrőt az oldalkezelő osztályra vagy műveletmetódusra:
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Egyéb Kestrel korlátok
Egyéb Kestrel korlátozások vonatkozhatnak a következő által üzemeltetett Kestrelalkalmazásokra:
IIS
Az alapértelmezett kérelemkorlát (maxAllowedContentLength) 30 000 000 bájt, ami körülbelül 28,6 MB. Állítsa be a korlátot a web.config fájlban. Az alábbi példában a korlát értéke 50 MB (52 428 800 bájt):
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
A maxAllowedContentLength beállítás csak az IIS-ra vonatkozik. További információ: Kérelemkorlátok <requestLimits>.
A HTTP-kérés maximális kérelemtörzsméretének növelése a következő beállítással IISServerOptions.MaxRequestBodySizeStartup.ConfigureServices: . Az alábbi példában a korlát értéke 50 MB (52 428 800 bájt):
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 52428800;
});
További információ: Host ASP.NET Core on Windows with IIS.
Troubleshoot
Az alábbiakban néhány gyakori probléma merült fel a fájlok feltöltése és lehetséges megoldásai során.
Nem található hiba az IIS-kiszolgálón való üzembe helyezéskor
A következő hiba azt jelzi, hogy a feltöltött fájl túllépi a kiszolgáló konfigurált tartalomhosszát:
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
További információ: IIS szakasz.
Kapcsolódási hiba
A kapcsolati hiba és a kiszolgáló alaphelyzetbe állítása valószínűleg azt jelzi, hogy a feltöltött fájl meghaladja Kestrela kérelem törzsének maximális méretét. További információkért tekintse meg a Kestrel kérelem törzsének maximális méretét ismertető szakaszt. Kestrel az ügyfélkapcsolat korlátainak módosítása is szükséges lehet.
Null hivatkozási kivétel az IFormFile-nal
Ha a vezérlő a feltöltött fájlokat IFormFile elfogadja, de az érték null, ellenőrizze, hogy a HTML-űrlapon az enctype értéke multipart/form-data-e. Ha ez az attribútum nincs beállítva az <form> elemen, a fájlfeltöltés nem történik meg, és a kötött IFormFile argumentumok nem érvényesek null. Győződjön meg arról is, hogy a feltöltés elnevezése űrlapadatokban megegyezik az alkalmazás elnevezésével.
A stream túl hosszú volt
A jelen témakör példái a feltöltött fájl tartalmának tárolására támaszkodnak MemoryStream . A MemoryStream maximális mérete int.MaxValue. Ha az alkalmazás fájlfeltöltési forgatókönyve 50 MB-nál nagyobb fájltartalmat igényel, használjon olyan alternatív módszert, amely nem támaszkodik egyetlen MemoryStream feltöltött fájl tartalmának tárolására.