2015-01-30 3 views
1

JavaScript에서 AJAX를 통해 전송되는 HTML 콘텐츠가 포함 된 PDF 파일을 생성하려고합니다..Net ApiController에서 파일 다운로드 시작

HTML을 제대로 보내고 HTML을 사용하여 PDF가 올바르게 생성되었지만 사용자 브라우저에서 파일 다운로드를 시작하는 방법을 파악하려고했습니다.

이것은 현재 내가 시도하고있는 것입니다. 이 방법은 파일의 URL을 직접 입력 할 때 IHttpHandler 파일에서 작동하지만 AJAX를 통해 게시 할 때는 ApiController에서 작동하지 않습니다.

같은 결과를 사용하여 IHttpHandler 파일에 게시를 시도했습니다. BinaryWrite을 사용하여 다운로드를 시작하기 전까지는 문제가 없습니다.

public class DownloadScheduleController : ApiController 
{ 
    public void Post(HtmlModel data) 
    { 
     var htmlContent = data.html; 

     var pdfBytes = (new NReco.PdfGenerator.HtmlToPdfConverter()).GeneratePdf(htmlContent); 

     using (var mstream = new System.IO.MemoryStream()) 
     { 
      HttpContext.Current.Response.ContentType = "application/pdf"; 
      HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=UserSchedule.pdf"); 
      HttpContext.Current.Response.BinaryWrite(pdfBytes); 
      HttpContext.Current.Response.End(); 
     } 
    } 
} 

다음은 Ajax 요청입니다. Angular의 $ http를 사용하고 있습니다.

$http({ 
    method: 'POST', 
    url: 'api/downloadSchedule', 
    dataType: "json", 
    contentType: "application/json; charset=utf-8", 
    data: data 
}); 

또는 PDF 바이너리를 자바 스크립트로 반환 할 수 있지만 JavaScript를 사용하여 PDF를 저장하려면 어떻게해야합니까?

도움이 될 것입니다.

+0

필자는 바이트 스트림과 'content-disposition : attachment'를 반환하는 비 API 컨트롤러로 실제로 리디렉션하여 (301을 통해)이 작업을 수행했습니다. 나는 그것이 AJAX로 할 수 있다면 100 % 확실하지 않다. (그러나 나는 분명히 틀릴 수있다 - 여기서 기억을 떠난다.) –

+0

예, 불행히도 AJAX를 통해 수행해야하는 상황에 처해 있습니다. PDF 생성기에 필요한 html 콘텐츠가 Javascript를 통해 다른 페이지에 동적으로로드됩니다. 그들이 다운로드 버튼을 클릭하면 iFrame을 사용하여 페이지를 트리거하고 콘텐츠를 가져오고 일단 콘텐츠가로드되면 downloadSchedule ApiController에 대한 API 호출을 트리거하는 이벤트가 발생합니다. – Kolby

+0

나는 2 가지 일을 시도 할 것이다 : Response.Flush(); 귀하의 Response.End() 전에; 및 Response.OutputStream.Write (pdfBytes, 0, pdfBytes.Length); 대신 Response.BinaryWrite (pdfBytes) – JFlox

답변

2

보안상의 이유로 브라우저는 AJAX 요청으로부터 파일 다운로드를 시작할 수 없습니다. 파일을 보내는 페이지로 이동해야만 수행 할 수 있습니다.

일반적으로 Javascript에서 다운로드를 시작해야하는 경우 window.location.href = urlToFile을 설정하거나 해당 URL을 가리키는 새 iframe을 만듭니다. 귀하의 경우에는

위의 방법은 GET 요청을 수행하기 때문에이 봉사 않을 것이다, 당신은 POST 필요, 그래서는이 두 가지 가능한 솔루션을 참조하십시오

  • 을 당신이 당신의 JS를 수정할 수 있습니다 - $ http로 요청을 제출하는 대신 - 원래 AJAX 요청으로 게시 한 것에 해당하는 필드가있는 HTML 양식을 만든 다음 양식을 페이지에 추가하고 필드를 채우고 양식을 제출하고
  • 또는 양식을 제출하십시오. 자바 스크립트뿐만 아니라 서버 측 코드.

두 번째 솔루션을 선택하는 경우, 당신은 서버 측에서 두 가지 방법을 사용하는 방법에 따라 수 :있다 (파일을 생성하고 캐시에 저장

  • 하나 POST를 캐시를 사용하는 것이 더 좋을 것 같습니다. 특히 서버 팜에있는 경우에는 더 편리합니다. 그러나 간단하게 유지합시다.)

  • 캐시 된 콘텐츠를 검색하여 사용자에게 반환하는 GET입니다. 이렇게하면 AJAX 호출을 통해 데이터를 게시 한 다음 올바른 URL로 이동하여 파일을 가져올 수 있습니다.

같은 것을 보일 것이다 귀하의 C# 코드 : 내 코드가 당신을 보여주기위한 것입니다 점을 명심

$http({ 
    method: 'POST', 
    url: 'api/downloadSchedule', 
    dataType: "json", 
    contentType: "application/json; charset=utf-8", 
    data: data 
}).success(function(response) { 
    // note: the url path might be different depending on your route configuration 
    window.location.href = 'api/downloadSchedule/' + response.id; 
}); 

:

public class DownloadScheduleController : ApiController 
{ 
    public object Post(HtmlModel data) 
    { 
     var htmlContent = data.html; 

     var pdfBytes = (new NReco.PdfGenerator.HtmlToPdfConverter()).GeneratePdf(htmlContent); 
     var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30), Priority = CacheItemPriority.NotRemovable }; 
     var cacheId = Guid.NewGuid().ToString(); 
     MemoryCache.Default.Add("pdfBytes_" + cacheId, pdfBytes, policy); 
     return new { id = cacheId }; 
    } 

    public void Get(string id) 
    { 
     var pdfBytes = MemoryCache.Default.Get("pdfBytes_" + id); 
     MemoryCache.Default.Remove("pdfBytes_" + id); 

     using (var mstream = new System.IO.MemoryStream()) 
     { 
      HttpContext.Current.Response.ContentType = "application/pdf"; 
      HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=UserSchedule.pdf"); 
      HttpContext.Current.Response.BinaryWrite(pdfBytes); 
      HttpContext.Current.Response.End(); 
     } 
    } 
} 

귀하의 프론트 엔드는 다음이 될 수있는 일 등을 이 방식은 프로덕션 환경에서 사용되는 방식으로 사용되지 않습니다. 예를 들어, 사용 직후 캐시를 명확히 지워야합니다. 또한 Get 메서드에서 캐시에서 검색된 내용을 사용하기 전에 정상 성 검사를 수행해야합니다. 또한 요구 사항에 따라 저장 및 검색하는 데이터에 대한 추가 보안 예방 조치를 취하는 것이 좋습니다.

+0

첫 번째 방법이 웹 폼과 함께 작동하는지 알고 있습니까? 두 번째 방법을 사용해 보았지만 데이터가 매우 클 수 있으며 많은 사용자가있는 대규모 응용 프로그램입니다. 이 경우에는 서버 쪽 저장소 옵션이 작동하지 않을 것이라고 생각합니다. – Kolby

+0

첫 번째 솔루션을 웹 폼 작업에 적용 할 수는 있지만 viewstate가 필요할 수 있으므로 미리 양식 요소를 정의한 다음 JS 함수에서 해당 요소를 채워 양식을 제출하는 대신 '__doPostBack()'을 호출해야합니다 . 두 번째 해결책은 왜 효과가 없을 것이라고 생각합니까? 많은 양의 데이터를 가지고 있다면 차이점이 없다고 생각합니다. 바이트 배열을 만들 자마자 이미 메모리에 모두 저장되어 있으므로 다음 요청을 위해이를 기억할 때의 문제점은 무엇입니까? –

+0

두 번째 방법을 사용하여 끝났으며 완벽하게 작동합니다. 대단히 감사합니다. 그것이 작동한다고 생각하지는 않았지만, 나는 도스 (또는 유사한) 공격에 대한 취약점을 여는 것에 관심이 있습니다. 나는 네가하는 말을 본다. 30 초 동안 기억하는 것은별로하지 않을 것이다. Get 함수가 실행 된 후 캐시를 제거하는 방법은 무엇입니까? – Kolby

관련 문제