Here are my requirements:Generate Excel reports for users, attaching them to emailsAdditionally, insert hyperlinks into those emails where the user can view the same report on a web page.I have a Windows forms util that I use to generate the Excel spreadsheets and email them. I have a Web API app that will serve the reports as web pages.There are two approaches that I have been able to come up with, but am wrangling with myself over which one would be better; alternatively, I am open to other ideas.Here are my two ideas:On creating the .xlsx file in the util, also save it as an .htm file (this can be done manually from Excel, so certainly it is possible programmatically, too). I could then POST the .htm file as a string to the Web API app (programmatically from the Windows forms util, without any user intervention required), and store that string (saving it as a file) in the Web API project's App_Data folder.Then, when the user clicks the hyperlink in the email, the Web API responds to this GET request by loading the file from the App_Data folder, reading it into a string and returning that.When the report is generated in the util, call a POST method in the Web API Controller that will run the same Stored Procedure to get the data for the report. Using Json.NET, save this data as a JSON file to the App_Data folder.When the user clicks the hyperlink in the email, respond to this GET request by opening the corresponding json file, converting it to html, and returning that.~({})~ ~({})~ ~({})~ ~({})~ ~({})~I believe the advantages of the first approach are:I don't need to execute the SP twiceI don't have to write all that JSON-to-HTML conversion codeI believe the disadvantages of the first approach are:Grid lines on the spreadsheet are not preserved (I manually saved a report as .htm, opened it in the browser, and that's the reality)The HTML generated in this way is pretty much immutable/ungrokkableHere's the HTML generated for one such report:<html xmlns:v=urn:schemas-microsoft-com:vmlxmlns:o=urn:schemas-microsoft-com:office:officexmlns:x=urn:schemas-microsoft-com:office:excelxmlns=http://www.w3.org/TR/REC-html40><head><meta name=Excel Workbook Frameset><meta http-equiv=Content-Type content=text/html; charset=windows-1252><meta name=ProgId content=Excel.Sheet><meta name=Generator content=Microsoft Excel 12><link rel=File-Listhref=Gramps%20-%20Platypus%20Compliance%20-%20September%202015_files/filelist.xml><![if !supportTabStrip]><link id=shLink href=Gramps%20-%20Platypus%20Compliance%20-%20September%202015_files/sheet001.htm><link id=shLink><script language=JavaScript><!-- var c_lTabs=1; var c_rgszSh=new Array(c_lTabs); c_rgszSh[0] = Platypus Compliance September 2015; var c_rgszClr=new Array(8); c_rgszClr[0]=window; c_rgszClr[1]=buttonface; c_rgszClr[2]=windowframe; c_rgszClr[3]=windowtext; c_rgszClr[4]=threedlightshadow; c_rgszClr[5]=threedhighlight; c_rgszClr[6]=threeddarkshadow; c_rgszClr[7]=threedshadow; var g_iShCur; var g_rglTabX=new Array(c_lTabs);function fnGetIEVer(){ var ua=window.navigator.userAgent var msie=ua.indexOf(MSIE) if (msie>0 && window.navigator.platform==Win32)  return parseInt(ua.substring(msie+5,ua.indexOf(., msie))); else  return 0;}function fnBuildFrameset(){ var szHTML=<frameset rows=\*,18\ border=0 width=0 frameborder=no framespacing=0>+  <frame src=\+document.all.item(shLink)[0].href+\ name=\frSheet\ noresize>+  <frameset cols=\54,*\ border=0 width=0 frameborder=no framespacing=0>+  <frame src=\\ name=\frScroll\ marginwidth=0 marginheight=0 scrolling=no>+  <frame src=\\ name=\frTabs\ marginwidth=0 marginheight=0 scrolling=no>+  </frameset></frameset><plaintext>; with (document) {  open(text/html,replace);  write(szHTML);  close(); } fnBuildTabStrip();}function fnBuildTabStrip(){ var szHTML=  <html><head><style>.clScroll {font:8pt Courier New;color:+c_rgszClr[6]+;cursor:default;line-height:10pt;}+  .clScroll2 {font:10pt Arial;color:+c_rgszClr[6]+;cursor:default;line-height:11pt;}</style></head>+  <body onclick=\event.returnValue=false;\ ondragstart=\event.returnValue=false;\ onselectstart=\event.returnValue=false;\ bgcolor=+c_rgszClr[4]+ topmargin=0 leftmargin=0><table cellpadding=0 cellspacing=0 width=100%>+  <tr><td colspan=6 height=1 bgcolor=+c_rgszClr[2]+></td></tr>+  <tr><td style=\font:1pt\>&nbsp;<td>+  <td valign=top id=tdScroll class=\clScroll\ onclick=\parent.fnFastScrollTabs(0);\ onmouseover=\parent.fnMouseOverScroll(0);\ onmouseout=\parent.fnMouseOutScroll(0);\><a>&#171;</a></td>+  <td valign=top id=tdScroll class=\clScroll2\ onclick=\parent.fnScrollTabs(0);\ ondblclick=\parent.fnScrollTabs(0);\ onmouseover=\parent.fnMouseOverScroll(1);\ onmouseout=\parent.fnMouseOutScroll(1);\><a>&lt</a></td>+  <td valign=top id=tdScroll class=\clScroll2\ onclick=\parent.fnScrollTabs(1);\ ondblclick=\parent.fnScrollTabs(1);\ onmouseover=\parent.fnMouseOverScroll(2);\ onmouseout=\parent.fnMouseOutScroll(2);\><a>&gt</a></td>+  <td valign=top id=tdScroll class=\clScroll\ onclick=\parent.fnFastScrollTabs(1);\ onmouseover=\parent.fnMouseOverScroll(3);\ onmouseout=\parent.fnMouseOutScroll(3);\><a>&#187;</a></td>+  <td style=\font:1pt\>&nbsp;<td></tr></table></body></html>; with (frames['frScroll'].document) {  open(text/html,replace);  write(szHTML);  close(); } szHTML =  <html><head>+  <style>A:link,A:visited,A:active {text-decoration:none;+color:+c_rgszClr[3]+;}+  .clTab {cursor:hand;background:+c_rgszClr[1]+;font:9pt Arial;padding-left:3px;padding-right:3px;text-align:center;}+  .clBorder {background:+c_rgszClr[2]+;font:1pt;}+  </style></head><body onload=\parent.fnInit();\ onselectstart=\event.returnValue=false;\ ondragstart=\event.returnValue=false;\ bgcolor=+c_rgszClr[4]+   topmargin=0 leftmargin=0><table id=tbTabs cellpadding=0 cellspacing=0>; var iCellCount=(c_lTabs+1)*2; var i; for (i=0;i<iCellCount;i+=2)  szHTML+=<col width=1><col>; var iRow; for (iRow=0;iRow<6;iRow++) {  szHTML+=<tr>;  if (iRow==5)   szHTML+=<td colspan=+iCellCount+></td>;  else {   if (iRow==0) {    for(i=0;i<iCellCount;i++)     szHTML+=<td height=1 class=\clBorder\></td>;   } else if (iRow==1) {    for(i=0;i<c_lTabs;i++) {     szHTML+=<td height=1 nowrap class=\clBorder\>&nbsp;</td>;     szHTML+=      <td id=tdTab height=1 nowrap class=\clTab\ onmouseover=\parent.fnMouseOverTab(+i+);\ onmouseout=\parent.fnMouseOutTab(+i+);\>+      <a href=\+document.all.item(shLink)[i].href+\ target=\frSheet\ id=aTab>&nbsp;+c_rgszSh[i]+&nbsp;</a></td>;    }    szHTML+=<td id=tdTab height=1 nowrap class=\clBorder\><a id=aTab>&nbsp;</a></td><td width=100%></td>;   } else if (iRow==2) {    for (i=0;i<c_lTabs;i++)     szHTML+=<td height=1></td><td height=1 class=\clBorder\></td>;    szHTML+=<td height=1></td><td height=1></td>;   } else if (iRow==3) {    for (i=0;i<iCellCount;i++)     szHTML+=<td height=1></td>;   } else if (iRow==4) {    for (i=0;i<c_lTabs;i++)     szHTML+=<td height=1 width=1></td><td height=1></td>;    szHTML+=<td height=1 width=1></td><td></td>;   }  }  szHTML+=</tr>; } szHTML+=</table></body></html>; with (frames['frTabs'].document) {  open(text/html,replace);  charset=document.charset;  write(szHTML);  close(); }}function fnInit(){ g_rglTabX[0]=0; var i; for (i=1;i<=c_lTabs;i++)  with (frames['frTabs'].document.all.tbTabs.rows[1].cells[fnTabToCol(i-1)])   g_rglTabX[i]=offsetLeft+offsetWidth-6;}function fnTabToCol(iTab){ return 2*iTab+1;}function fnNextTab(fDir){ var iNextTab=-1; var i; with (frames['frTabs'].document.body) {  if (fDir==0) {   if (scrollLeft>0) {    for (i=0;i<c_lTabs&&g_rglTabX[i]<scrollLeft;i++);    if (i<c_lTabs)     iNextTab=i-1;   }  } else {   if (g_rglTabX[c_lTabs]+6>offsetWidth+scrollLeft) {    for (i=0;i<c_lTabs&&g_rglTabX[i]<=scrollLeft;i++);    if (i<c_lTabs)     iNextTab=i;   }  } } return iNextTab;}function fnScrollTabs(fDir){ var iNextTab=fnNextTab(fDir); if (iNextTab>=0) {  frames['frTabs'].scroll(g_rglTabX[iNextTab],0);  return true; } else  return false;}function fnFastScrollTabs(fDir){ if (c_lTabs>16)  frames['frTabs'].scroll(g_rglTabX[fDir?c_lTabs-1:0],0); else  if (fnScrollTabs(fDir)>0) window.setTimeout(fnFastScrollTabs(+fDir+);,5);}function fnSetTabProps(iTab,fActive){ var iCol=fnTabToCol(iTab); var i; if (iTab>=0) {  with (frames['frTabs'].document.all) {   with (tbTabs) {    for (i=0;i<=4;i++) {     with (rows[i]) {      if (i==0)       cells[iCol].style.background=c_rgszClr[fActive?0:2];      else if (i>0 && i<4) {       if (fActive) {        cells[iCol-1].style.background=c_rgszClr[2];        cells[iCol].style.background=c_rgszClr[0];        cells[iCol+1].style.background=c_rgszClr[2];       } else {        if (i==1) {         cells[iCol-1].style.background=c_rgszClr[2];         cells[iCol].style.background=c_rgszClr[1];         cells[iCol+1].style.background=c_rgszClr[2];        } else {         cells[iCol-1].style.background=c_rgszClr[4];         cells[iCol].style.background=c_rgszClr[(i==2)?2:4];         cells[iCol+1].style.background=c_rgszClr[4];        }       }      } else       cells[iCol].style.background=c_rgszClr[fActive?2:4];     }    }   }   with (aTab[iTab].style) {    cursor=(fActive?default:hand);    color=c_rgszClr[3];   }  } }}function fnMouseOverScroll(iCtl){ frames['frScroll'].document.all.tdScroll[iCtl].style.color=c_rgszClr[7];}function fnMouseOutScroll(iCtl){ frames['frScroll'].document.all.tdScroll[iCtl].style.color=c_rgszClr[6];}function fnMouseOverTab(iTab){ if (iTab!=g_iShCur) {  var iCol=fnTabToCol(iTab);  with (frames['frTabs'].document.all) {   tdTab[iTab].style.background=c_rgszClr[5];  } }}function fnMouseOutTab(iTab){ if (iTab>=0) {  var elFrom=frames['frTabs'].event.srcElement;  var elTo=frames['frTabs'].event.toElement;  if ((!elTo) ||   (elFrom.tagName==elTo.tagName) ||   (elTo.tagName==A && elTo.parentElement!=elFrom) ||   (elFrom.tagName==A && elFrom.parentElement!=elTo)) {   if (iTab!=g_iShCur) {    with (frames['frTabs'].document.all) {     tdTab[iTab].style.background=c_rgszClr[1];    }   }  } }}function fnSetActiveSheet(iSh){ if (iSh!=g_iShCur) {  fnSetTabProps(g_iShCur,false);  fnSetTabProps(iSh,true);  g_iShCur=iSh; }} window.g_iIEVer=fnGetIEVer(); if (window.g_iIEVer>=4)  fnBuildFrameset();//--></script><![endif]><!--[if gte mso 9]><xml> <x:ExcelWorkbook>  <x:ExcelWorksheets>   <x:ExcelWorksheet>    <x:Name>Platypus Compliance September 2015</x:Name>    <x:WorksheetSource     HRef=Gramps%20-%20Platypus%20Compliance%20-%20September%202015_files/sheet001.htm/>   </x:ExcelWorksheet>  </x:ExcelWorksheets>  <x:Stylesheet   HRef=Gramps%20-%20Platypus%20Compliance%20-%20September%202015_files/stylesheet.css/>  <x:WindowHeight>14370</x:WindowHeight>  <x:WindowWidth>18360</x:WindowWidth>  <x:WindowTopX>480</x:WindowTopX>  <x:WindowTopY>75</x:WindowTopY>  <x:ProtectStructure>False</x:ProtectStructure>  <x:ProtectWindows>False</x:ProtectWindows> </x:ExcelWorkbook></xml><![endif]--></head><frameset rows=*,39 border=0 width=0 frameborder=no framespacing=0> <frame src=Gramps%20-%20Platypus%20Compliance%20-%20September%202015_files/sheet001.htm name=frSheet> <frame src=Gramps%20-%20Platypus%20Compliance%20-%20September%202015_files/tabstrip.htm name=frTabs marginwidth=0 marginheight=0> <noframes>  <body>   <p>This page uses frames, but your browser doesn't support them.</p>  </body> </noframes></frameset></html>On the other hand, I believe the advantages of the second approach are:I don't have to pass so much data to the Web API app in the POSTI have full control over the CSS/HTML (being able to add gridlines and whatever else is required (there will probably be special requests for style changes, etc.)I believe the disadvantages of the second approach are:Running the same SP twiceWriting all that JSON-to-HTML conversion codeIf there's an easier way, I'd be ecstatic to hear about it.
Generating dual .xlsx files and .htm pages of the same data
c#;html;excel