ASP.NET वेब एपीआई में कंट्रोलर से बाइनरी फाइल लौटना


323

मैं ASP.NET MVC के नए वेबएपीआई का उपयोग कर एक वेब सेवा पर काम कर रहा हूं जो बाइनरी फाइलों, ज्यादातर .cabऔर .exeफाइलों की सेवा करेगी ।

निम्न नियंत्रक विधि काम करने लगती है, जिसका अर्थ है कि यह एक फ़ाइल लौटाती है, लेकिन यह सामग्री प्रकार को application/jsonनिम्न पर सेट कर रही है :

public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
    var path = @"C:\Temp\test.exe";
    var stream = new FileStream(path, FileMode.Open);
    return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}

क्या ऐसा करने के लिए इससे अच्छा तरीका है?


2
कोई भी व्यक्ति जो वेब एपी और IHTTPActionResult के माध्यम से स्ट्रीम के माध्यम से एक बाइट सरणी वापस करना चाहता है, तो यहां देखें: nodogmablog.bryanhogan.net/2017/02/…
IbrarMumtaz

जवाबों:


516

सेट करने के लिए HttpResponseMessageअपनी Contentसंपत्ति के साथ एक सरल का उपयोग करने का प्रयास करें StreamContent:

// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;

public HttpResponseMessage Post(string version, string environment,
    string filetype)
{
    var path = @"C:\Temp\test.exe";
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = 
        new MediaTypeHeaderValue("application/octet-stream");
    return result;
}

streamप्रयुक्त चीजों पर ध्यान देने योग्य कुछ बातें :

  • आपको कॉल नहीं करना चाहिए stream.Dispose(), क्योंकि वेब API को तब भी इसे एक्सेस करने में सक्षम होने की आवश्यकता होती है जब यह resultक्लाइंट के डेटा को वापस भेजने के लिए नियंत्रक विधि की प्रक्रिया करता है । इसलिए, एक using (var stream = …)ब्लॉक का उपयोग न करें । वेब एपीआई आपके लिए धारा का निपटान करेगा।

  • सुनिश्चित करें कि स्ट्रीम में इसकी वर्तमान स्थिति 0 पर सेट है (यानी स्ट्रीम के डेटा की शुरुआत)। उपरोक्त उदाहरण में, यह एक दिया गया है क्योंकि आपने केवल फ़ाइल खोली है। हालांकि, अन्य परिदृश्यों में (जैसे कि जब आप पहली बार कुछ बाइनरी डेटा लिखते हैं MemoryStream), तो सुनिश्चित करें stream.Seek(0, SeekOrigin.Begin);या सेट करेंstream.Position = 0;

  • फ़ाइल धाराओं के साथ, स्पष्ट रूप से निर्दिष्ट FileAccess.Readअनुमति वेब सर्वर पर अधिकारों के उपयोग के मुद्दों को रोकने में मदद कर सकती है; IIS एप्लिकेशन पूल खाते अक्सर wwwroot पर केवल पढ़ने / सूची / एक्सेस अधिकारों का निष्पादन करते हैं।


37
क्या आपको पता होगा कि धारा कब बंद होगी? मैं यह मान रहा हूं कि फ्रेमवर्क अंततः HttpResponseMessage.Dispose () कहता है, जो बदले में HttpResponseMessage.Content.Dispose () को प्रभावी रूप से स्ट्रीम को बंद करने का आह्वान करता है।
स्टीव गाइडी

41
स्टीव - आप सही हैं और मैंने फाइलस्ट्रीम में एक ब्रेकप्वाइंट जोड़कर सत्यापित किया है। इस कोड को चलाएं और चलाएं। फ्रेमवर्क HttpResponseMessage.Dispose कहता है, जो StreamContent.Dispose कहता है, जो FileStream.Dispose कहता है।
डैन गार्टनर

15
आप वास्तव usingमें परिणाम ( HttpResponseMessage) या स्वयं स्ट्रीम को जोड़ नहीं सकते , क्योंकि वे अभी भी विधि के बाहर उपयोग किए जाएंगे। जैसा कि @ ने उल्लेख किया है, ग्राहक द्वारा प्रतिक्रिया भेजने के बाद उन्हें फ्रेमवर्क द्वारा निपटाया जाता है।
कारलोसफैगीरा

2
@ B.ClayShannon हाँ, इसके बारे में है। जहाँ तक ग्राहक का संबंध है यह HTTP प्रतिक्रिया की सामग्री में सिर्फ बाइट्स का एक गुच्छा है। ग्राहक उन बाइट्स के साथ जो कुछ भी चुनते हैं, उसे स्थानीय फ़ाइल में सहेजने सहित कर सकता है।
कार्लोसफैजीरा

5
@carlosfigueira, हाय, क्या आपको पता है कि बाइट्स भेजने के बाद फाइल को कैसे डिलीट करना है?
Zach

137

के लिए वेब एपीआई 2 , आप लागू कर सकते हैं IHttpActionResult। ये मेरा:

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

class FileResult : IHttpActionResult
{
    private readonly string _filePath;
    private readonly string _contentType;

    public FileResult(string filePath, string contentType = null)
    {
        if (filePath == null) throw new ArgumentNullException("filePath");

        _filePath = filePath;
        _contentType = contentType;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StreamContent(File.OpenRead(_filePath))
        };

        var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);

        return Task.FromResult(response);
    }
}

तब आपके नियंत्रक में ऐसा कुछ होता है:

[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
    var serverPath = Path.Combine(_rootPath, imagePath);
    var fileInfo = new FileInfo(serverPath);

    return !fileInfo.Exists
        ? (IHttpActionResult) NotFound()
        : new FileResult(fileInfo.FullName);
}

और यहां एक तरीका है कि आप IIS को विस्तार के साथ अनुरोधों को अनदेखा करने के लिए कह सकते हैं ताकि अनुरोध इसे नियंत्रक को दे देगा:

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>

1
अच्छा जवाब, हमेशा एसओ कोड चिपकाने के बाद और अलग-अलग मामलों (अलग-अलग फाइलों) के लिए नहीं चलता है।
क्रिज़ीस्तोफ़ मोरीस्क

1
@JonyAdamit धन्यवाद मुझे लगता है कि एक और विकल्प asyncविधि हस्ताक्षर पर एक संशोधक लगाने और एक कार्य के निर्माण को पूरी तरह से हटाने के लिए है: gist.github.com/ronnieoverby/ae0982c7832c531a9022
Ronnie Overby

4
इस चल रहे IIS7 + पर आने वाले किसी भी व्यक्ति के लिए बस एक सिर। runAllManagedModulesForAllRequests को अब छोड़ा जा सकता है
सूचकांक

1
@BendEg लगता है जैसे एक बार मैंने स्रोत की जाँच की और यह किया। और यह समझ में आता है कि यह होना चाहिए। ढांचे के स्रोत को नियंत्रित करने में सक्षम नहीं होने के कारण, इस प्रश्न का कोई भी उत्तर समय के साथ बदल सकता है।
रॉनी ओवरबी

1
वास्तव में पहले से ही FileResult (और यहां तक ​​कि FileStreamResult) वर्ग में बनाया गया है।
ब्रेनस्ल्गस83

12

.NET कोर का उपयोग करने वालों के लिए:

आप एक एपीआई नियंत्रक विधि में IActionResult इंटरफ़ेस का उपयोग कर सकते हैं, जैसे ...

    [HttpGet("GetReportData/{year}")]
    public async Task<IActionResult> GetReportData(int year)
    {
        // Render Excel document in memory and return as Byte[]
        Byte[] file = await this._reportDao.RenderReportAsExcel(year);

        return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
    }

यह उदाहरण सरल है, लेकिन बिंदु को पार करना चाहिए। नेट कोर में इस प्रक्रिया है इसलिए ज्यादा नेट के पिछले संस्करणों में से सरल - यानी कोई सेटिंग प्रतिक्रिया प्रकार, सामग्री, हेडर, आदि

इसके अलावा, निश्चित रूप से फ़ाइल के लिए MIME प्रकार और विस्तार व्यक्तिगत आवश्यकताओं पर निर्भर करेगा।

संदर्भ: SO पोस्ट उत्तर @NKosi द्वारा


1
बस एक नोट, अगर यह एक छवि है और आप इसे सीधे URL एक्सेस वाले ब्राउज़र में देखना चाहते हैं, तो फ़ाइल नाम की आपूर्ति न करें।
प्लूटो

9

जबकि सुझाया गया समाधान ठीक काम करता है, नियंत्रक से एक बाइट सरणी वापस करने का एक और तरीका है, प्रतिक्रिया स्ट्रीम के साथ ठीक से स्वरूपित:

  • अनुरोध में, हेडर "स्वीकार करें: एप्लिकेशन / ऑक्टेट-स्ट्रीम" सेट करें।
  • सर्वर-साइड, इस माइम प्रकार का समर्थन करने के लिए एक मीडिया प्रकार फ़ॉर्मेटर जोड़ें।

दुर्भाग्य से, WebApi में "एप्लिकेशन / ऑक्टेट-स्ट्रीम" के लिए कोई भी फ़ॉर्मेटर शामिल नहीं है। GitHub पर यहां एक कार्यान्वयन है: BinaryMediaTypeFormatter (वेबपी 2 के लिए इसे काम करने के लिए मामूली अनुकूलन हैं, विधि हस्ताक्षर बदल गए)।

आप इस फ़ॉर्मेटर को अपने वैश्विक कॉन्फ़िगर में जोड़ सकते हैं:

HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));

BinaryMediaTypeFormatterयदि अनुरोध सही शीर्षक हेडर निर्दिष्ट करता है, तो WebApi का उपयोग करना चाहिए ।

मैं इस समाधान को पसंद करता हूं क्योंकि एक एक्शन कंट्रोलर बाइट लौटाता है [] परीक्षण के लिए अधिक आरामदायक है। हालाँकि, यदि आप "एप्लिकेशन / ऑक्टेट-स्ट्रीम" (उदाहरण के लिए "छवि / gif") की तुलना में किसी अन्य सामग्री-प्रकार को वापस करना चाहते हैं, तो दूसरा समाधान आपको अधिक नियंत्रण की अनुमति देता है।


8

स्वीकृत उत्तर में विधि का उपयोग करते हुए एक बड़ी फ़ाइल को डाउनलोड करते समय एपीआई की समस्या को एक से अधिक बार कॉल किए जाने के लिए, कृपया सच्चे सिस्टम पर प्रतिक्रिया बफ़रिंग सेट करें। Web.HttpContext.Current.Response.Buffer = true;

यह सुनिश्चित करता है कि क्लाइंट को भेजे जाने से पहले पूरी बाइनरी सामग्री सर्वर साइड पर बफ़र की जाती है। अन्यथा आप नियंत्रक को भेजे जाने वाले कई अनुरोध देखेंगे और यदि आप इसे ठीक से नहीं संभालते हैं, तो फ़ाइल भ्रष्ट हो जाएगी।


3
Bufferसंपत्ति पदावनत किया गया है के पक्ष में BufferOutput। यह करने के लिए चूक true
घटता है

6

आपके द्वारा उपयोग किया जा रहा अधिभार क्रमिक स्वरूपणों की गणना सेट करता है। आपको सामग्री प्रकार स्पष्ट रूप से निर्दिष्ट करने की आवश्यकता है:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

3
उत्तर के लिए धन्यवाद। मैंने इसकी कोशिश की, और मैं अभी भी Content Type: application/jsonफिडलर में देख रहा हूं । Content Typeअगर मैं लौटने से पहले तोड़ सही तरीके से सेट किया गया प्रतीत होता httpResponseMessageप्रतिक्रिया। कोई और विचार?
जोश अर्ल

3

तुम कोशिश कर सकते हो

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.