I am using this code to try and render a razor partial view as a string for the purposes of sending an email.
public static string RenderPartialToString(
string userControlPath,
object viewModel,
ControllerContext controllerContext,
TempDataDictionary tempData)
{
using (var writer = new StringWriter())
{
var viewDataDictionary = new ViewDataDictionary(viewModel);
var view = new WebFormView(controllerContext, userControlPath);
var viewContext = new ViewContext(
controllerContext,
view,
viewDataDictionary,
tempData,
writer
);
viewContext.View.Render(viewContext, writer);
return writer.GetStringBuilder().ToString();
}
}
The problem is that I get the follow error:
must derive from ViewPage, ViewPage<TModel>, ViewUserControl, or ViewUserControl<TModel>. Stack Trace: at System.Web.Mvc.WebFormView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) at .... RenderPartialToString
How would I fix that ?
Indeed, WebFormView doesn't inherit from the mentioned classes, just IView. I did a little Google research and got a prototype working. This page was the most helpful.
I created an empty MVC3 application and created the following HomeController. When I run the application, the page shows the rendered string. The resultAsString variable shows how to capture the rendering as a string.
using System;
using System.IO;
using System.Web.Mvc;
public class HomeController : Controller
{
public ActionResult Index()
{
var result = RenderPartial(this.ControllerContext, "This is #DateTime.Now right now");
var resultAsString = result.Content;
return result;
}
private ContentResult RenderPartial(ControllerContext controllerContext, string template)
{
var temporaryViewPath = string.Format("~/Views/{0}.cshtml", Guid.NewGuid());
using (var stringWriter = new StringWriter())
{
using (var fileStream = System.IO.File.Create(Server.MapPath(temporaryViewPath)))
{
using (var streamWriter = new StreamWriter(fileStream))
{
streamWriter.WriteLine(template);
streamWriter.Close();
}
fileStream.Close();
}
var razor = new RazorView(controllerContext, temporaryViewPath, null, false, null);
razor.Render(new ViewContext(controllerContext, razor, new ViewDataDictionary(), new TempDataDictionary(), stringWriter), stringWriter);
System.IO.File.Delete(Server.MapPath(temporaryViewPath));
return Content(stringWriter.ToString());
}
}
}
Related
I am trying to do an export to excel button when it gets hit I want to call this api and download an excel sheet.
my controller code looks like this and I am honestly stuck because I am unsure what I should be returning back. I am new to .net core api sorry
[Route("api/excel")]
public class ExportToExcel : Controller
{
private readonly ScadaContext _context;
public ExportToExcel(ScadaContext context)
{
_context = context;
}
// GET: api/values
[HttpGet]
public ActionResult GetExcelMainView()
{
var query = _context.Main_View.Where(x => !x.MeterId.StartsWith("HOGC"));
List<Object[]> MainViewList = new List<Object[]>();
foreach(var p in query)
{
MainViewList.Add(new Object[] { "Battery Voltage", p.BatteryVoltage });
}
MemoryStream stream = new MemoryStream();
using (ExcelPackage pck = new ExcelPackage(stream))
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("MainView");
ws.Cells["A1"].LoadFromArrays(MainViewList);
Response.Clear();
Response.Headers.Add("content-disposition", "attachment; filename=Clients.xlsx");
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var bytes = pck.GetAsByteArray();
Response.Body.WriteAsync(bytes, 0, bytes.Length);
}
}
}
Your simplest way to use one of the FileResult subclassess,
such as FileStreamResult
The controller method File(stream,...,...) does it for you:
return File(stream, contentTypeString, willbeSavedWithThisFileName);
should just work. I'm not sure if you'll have to re-"wrap" your package in a memorystream again
var streamagain = new MemoryStream(package.GetAsByteArray());
(with the "handrolled" code you've already written, I'd try return StatusCode(200) as the way to get your Content back to the client).
I am trying to expose a REST API using Azure Functions which returns terms from a specific termset in SharePoint Online using CSOM and C#.
I can definitely invoke this exact same CSOM code from a console app and from an Azure API app and it is able to loop through the terms and output to console or the HTTP response successfully.
However, when the code below is invoked from the Azure Function host, it ALWAYS find a collection of NULL term objects, when looping through the TermCollection or the IEnumerable<Term> (I’ve tried by using ClientContext.LoadQuery on TermSet.GetAllTerms(), as well as by just loading the TermCollection via the TermSet.Terms property).
As soon as the iterator hits a term in the foreach (which I’ve also tried as just a LINQ Select), it thinks that the item is NULL, so calling properties on it throws the NullReferenceException. I cannot reproduce the behavior from the console app or from the API app calling into the same code - it just works as expected there and retrieves each Term object.
Why is this happening when SAME CODE is invoked from different hosts??
Why would this happen in the Azure Functions host, but not in Console app or the Azure API app?
What is the difference when invoked from an Azure Function host??
I would really like to use Azure Functions for the consumption pricing benefits, so I don't have to host this in an App Service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;
namespace CsomTaxonomyHelper
{
public class TermSearch
{
private readonly ClientContext ctx;
public TermSearch(ClientContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
ctx = context;
}
public IEnumerable<TermViewModel> GetTerms(Guid termSetId)
{
var taxonomySession = TaxonomySession.GetTaxonomySession(ctx);
var termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
var termSet = termStore.GetTermSet(termSetId);
//get flat list of terms, so we don't make recursive calls to SPO
var allTerms = ctx.LoadQuery(termSet.GetAllTerms().IncludeWithDefaultProperties());
ctx.ExecuteQuery();
return ToViewModel(allTerms);
}
static IEnumerable<TermViewModel> ToViewModel(IEnumerable<Term> allTerms)
{
var results = allTerms.Select(term => new TermViewModel
{
Id = term.Id, //BOOM! <-- within the context of an Azure Function the "allTerms" IEnumerable is a list of nulls
Name = term.Name,
ParentId = TryGetParentId(term)
});
return results;
}
static Guid? TryGetParentId(Term term)
{
try
{
if (term.Parent.IsPropertyAvailable("Id"))
return term.Parent.Id;
}
catch (ServerObjectNullReferenceException) { }
return null;
}
}
public class PasswordString
{
public SecureString SecurePassword { get; private set; }
public PasswordString(string password)
{
SecurePassword = new SecureString();
foreach (char c in password.ToCharArray())
{
SecurePassword.AppendChar(c);
}
SecurePassword.MakeReadOnly();
}
}
}
Here's the "run.csx" function, invoking the code above which has been compiled into a DLL and placed in the Bin folder of the Azure Function:
#r "CsomTaxonomyHelper.dll"
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;
using CsomTaxonomyHelper;
using Newtonsoft.Json;
static TraceWriter _log = null;
public static HttpResponseMessage Run(HttpRequestMessage req, TraceWriter log)
{
_log = log;
_log.Info("C# HTTP trigger function processed a request. Getting mmd terms from SPO...");
var terms = GetFocusAreas();
var result = JsonConvert.SerializeObject(terms);
return req.CreateResponse(HttpStatusCode.OK, result);
}
static IEnumerable<TermViewModel> GetFocusAreas()
{
string spSiteUrl = System.Environment.GetEnvironmentVariable("SPOSiteUrl", EnvironmentVariableTarget.Process);
string userName = System.Environment.GetEnvironmentVariable("SPOUserName", EnvironmentVariableTarget.Process);
string password = System.Environment.GetEnvironmentVariable("SPOPassword", EnvironmentVariableTarget.Process);
var securePwd = new PasswordString(password).SecurePassword;
using (var ctx = new ClientContext(spSiteUrl))
{
ctx.Credentials = new SharePointOnlineCredentials(userName, securePwd);
ctx.ExecuteQuery();
_log.Info("Logged into SPO service.");
var search = new TermSearch(ctx);
try
{
var result = search.GetTerms(new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"));
return result;
}
catch (Exception ex)
{
_log.Error(ex.Message, ex);
throw;
}
}
}
Project.json:
{
"frameworks": {
"net46":{
"dependencies": {
"Microsoft.SharePointOnline.CSOM": "16.1.6112.1200"
}
}
}
}
Here's the screenshot of the local debugger, when using the Azure Functions CLI to debug this (you can see that it did find 10 items in the collection, but all items are null):
Not the solution, but adding to the conversation - I was able to test with PnP-PowerShell (2017-Feb). Terms were just added.
SPO, CSOM and PnP-PowerShell.
Installing PnP-PowerShell to a PowerShell function:
I have the following code on server:
public class UploadController : ApiController
{
public void Put(string filename, string description)
{
...
}
public void Put()
{
...
}
and try to call it from client:
var clientDescr = new HttpClient();
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("filename", "test"));
postData.Add(new KeyValuePair<string, string>("description", "100"));
HttpContent contentDescr = new FormUrlEncodedContent(postData);
clientDescr.PutAsync("http://localhost:8758/api/upload", contentDescr).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
but this code calls second put method (without parameters). Why and how to call first put method correctly?
You have several options here:
You can either choose to pass the parameters in the query string, by just changing the URI to:
http://localhost:8758/api/upload?filename=test&description=100
or you can have Web API parse the form data for you by changing your action to look like this:
public void Put(FormDataCollection formData)
{
string fileName = formData.Get("fileName");
string description = formData.Get("description");
}
You can also choose to create a class that has a fileName and a description property and use that as your parameter and Web API should be able to bind it correctly for you.
We're making an ASP.Net MVC app that needs to be able to generate a PDF and display it to the screen or save it somewhere easy for the user to access. We're using PdfSharp to generate the document. Once it's finished, how do we let the user save the document or open it up in a reader? I'm especially confused because the PDF is generated server-side but we want it to show up client-side.
Here is the MVC controller to create the report that we have written so far:
public class ReportController : ApiController
{
private static readonly string filename = "report.pdf";
[HttpGet]
public void GenerateReport()
{
ReportPdfInput input = new ReportPdfInput()
{
//Empty for now
};
var manager = new ReportPdfManagerFactory().GetReportPdfManager();
var documentRenderer = manager.GenerateReport(input);
documentRenderer.PdfDocument.Save(filename); //Returns a PdfDocumentRenderer
Process.Start(filename);
}
}
When this runs, I get an UnauthorizedAccessException at documentRenderer.PdfDocument.Save(filename); that says, Access to the path 'C:\Program Files (x86)\Common Files\Microsoft Shared\DevServer\10.0\report.pdf' is denied. I'm also not sure what will happen when the line Process.Start(filename); is executed.
This is the code in manager.GenerateReport(input):
public class ReportPdfManager : IReportPdfManager
{
public PdfDocumentRenderer GenerateReport(ReportPdfInput input)
{
var document = CreateDocument(input);
var renderer = new PdfDocumentRenderer(true, PdfSharp.Pdf.PdfFontEmbedding.Always);
renderer.Document = document;
renderer.RenderDocument();
return renderer;
}
private Document CreateDocument(ReportPdfInput input)
{
//Put content into the document
}
}
Using Yarx's suggestion and PDFsharp Team's tutorial, this is the code we ended up with:
Controller:
[HttpGet]
public ActionResult GenerateReport(ReportPdfInput input)
{
using (MemoryStream stream = new MemoryStream())
{
var manager = new ReportPdfManagerFactory().GetReportPdfManager();
var document = manager.GenerateReport(input);
document.Save(stream, false);
return File(stream.ToArray(), "application/pdf");
}
}
ReportPdfManager:
public PdfDocument GenerateReport(ReportPdfInput input)
{
var document = CreateDocument(input);
var renderer = new PdfDocumentRenderer(true,
PdfSharp.Pdf.PdfFontEmbedding.Always);
renderer.Document = document;
renderer.RenderDocument();
return renderer.PdfDocument;
}
private Document CreateDocument(ReportPdfInput input)
{
//Creates a Document and puts content into it
}
I'm not familar with PDF sharp but for MVC is mostly done via built in functionality. You need to get your pdf document represented as an array of bytes. Then you'd simply use MVC's File method to return it to the browser and let it handle the download. Are there any methods on their class to do that?
public class PdfDocumentController : Controller
{
public ActionResult GenerateReport(ReportPdfInput input)
{
//Get document as byte[]
byte[] documentData;
return File(documentData, "application/pdf");
}
}
I have a WCF REST service that takes some parameters and sends an email. The template for the email is an MVC3 action. Essentially I want to render that action to a string.
If it were an ASP.NET WebForm, I could simply use Server.Execute(path, stringWriter, false). However when I plug in the path to my action, I get Error executing child request.
I have full access to HttpContext from my service (AspNetCompatibilityRequirementsMode.Allowed).
I know there are other answers out there for rendering actions to strings from within the context of a controller. How do I do this when I'm outside that world, but still on the same server (and, for that matter, in the same app)?
I cobbled together an answer based on several different google searches. It works, but I'm not 100% sure it's as lean as it could be. I'll paste the code for others to try.
string GetEmailText(TemplateParameters parameters) {
// Get the HttpContext
HttpContextBase httpContextBase =
new HttpContextWrapper(HttpContext.Current);
// Build the route data
var routeData = new RouteData();
routeData.Values.Add("controller", "EmailTemplate");
routeData.Values.Add("action", "Create");
// Create the controller context
var controllerContext = new ControllerContext(
new RequestContext(httpContextBase, routeData),
new EmailTemplateController());
var body = ((EmailTemplateController)controllerContext.Controller)
.Create(parameters).Capture(controllerContext);
return body;
}
// Using code from here:
// http://blog.approache.com/2010/11/render-any-aspnet-mvc-actionresult-to.html
public class ResponseCapture : IDisposable
{
private readonly HttpResponseBase response;
private readonly TextWriter originalWriter;
private StringWriter localWriter;
public ResponseCapture(HttpResponseBase response)
{
this.response = response;
originalWriter = response.Output;
localWriter = new StringWriter();
response.Output = localWriter;
}
public override string ToString()
{
localWriter.Flush();
return localWriter.ToString();
}
public void Dispose()
{
if (localWriter != null)
{
localWriter.Dispose();
localWriter = null;
response.Output = originalWriter;
}
}
}
public static class ActionResultExtensions
{
public static string Capture(this ActionResult result, ControllerContext controllerContext)
{
using (var it = new ResponseCapture(controllerContext.RequestContext.HttpContext.Response))
{
result.ExecuteResult(controllerContext);
return it.ToString();
}
}
}