I am trying to generate a PDF via Azure function using DinkToPdf. This what I have done so far.
[FunctionName("GeneratePdf")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log,
ExecutionContext executionContext)
{
string name = await GetName(req);
return CreatePdf(name, executionContext);
}
private static ActionResult CreatePdf(string name, ExecutionContext executionContext)
{
var globalSettings = new GlobalSettings
{
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4,
Margins = new MarginSettings { Top = 10 },
};
var objectSettings = new ObjectSettings
{
PagesCount = true,
WebSettings = { DefaultEncoding = "utf-8" },
HtmlContent = $#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
Hello, ${name}
</body>
</html>",
};
var pdf = new HtmlToPdfDocument()
{
GlobalSettings = globalSettings,
Objects = { objectSettings }
};
byte[] pdfBytes = IocContainer.Resolve<IConverter>().Convert(pdf);
return new FileContentResult(pdfBytes, "application/pdf");
}
This is working pretty good, when I am testing the function on local. However, it is not working as expected when deployed to Azure.
The primary problem is that in the places of the texts in the pdf, boxes are appearing (see below for example).
Moreover the response is also excruciatingly slow. Is there a way to improve/correct this?
Additional Info:
I am also using unity IOC to resolve IConverter. The type registration looks something like below:
var container = new UnityContainer();
container.RegisterType<IConverter>(
new ContainerControlledLifetimeManager(),
new InjectionFactory(c => new SynchronizedConverter(new PdfTools()))
);
I have tried couple of other NuGet packages such as PdfSharp, MigraDoc, Select.HtmlToPdf.NetCore, etc. But alll of those have dependency on System.Drawing.Common, which is not usable in Azure function.
The issue appears to be related to the restrictions of the Azure Function in "Consumption" mode. If you use "App Mode", it should work.
See the discussion below this Gist for some users who had success converting their Azure Function to "App Mode".
Related
I was following the following official documentation: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0#access-configuration-in-razor-pages
This implementation of #Configuration["myKey"] works perfectly for code that is not in between #{} brackets, but when I have a code block it simply does not work.
The documentation provides no code examples as far as I can see...
How do I solve this problem?
The code is of course in a Razor page (.cshtml).
I tried removing the # and putting in the same code without the #, but then it gives a context error...
P.S. the code in question is a POCO if it matters.
P.P.S. I use #inject IConfiguration Configuration for importing the configuration at the top of the Razor page.
My problematic code:
var website = new WebSite()
{
private string altName = Configuration["FrameworkData:PageTitle"] +" - " +Configuration["FrameworkData:Slogan"];
AlternateName = altName,
....
I've already tried specifying the IConfiguration Type before the Configuration specification without any avail.
UPDATE
My starting code with the problematic parts in it:
#using Microsoft.AspNetCore.Http;
#using Schema.NET;
#model projectname2.Areas.MyFeature.Pages.Shared._LayoutModel
#using Microsoft.Extensions.Configuration
#inject IConfiguration Configuration
#{
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name='description' content='#ViewData["Description"]'>
<title>#ViewData["Title"] - #Configuration["FrameworkData:PageTitle"]</title>
#{
var website = new WebSite()
{
private string altName = "";
string altName = $"{Configuration["FrameworkData:PageTitle"]} - {Configuration["FrameworkData:Slogan"]}";
AlternateName = altName,
Name = Configuration["FrameworkData:PageTitle"],
Url = new Uri("https://example.com")
};
var jsonLd = website.ToString();
var address = new PostalAddress()
{
StreetAddress = "Example street 10",
AddressLocality = "NY",
PostalCode = "11111",
AddressCountry = "US"
};
var geo = new GeoCoordinates()
{
Latitude = 0.3947623,
Longitude = 0.8723408
};
var localbusiness = new LocalBusiness()
{
Name = "Project name",
Image = new Uri("https://example.com/graphics/logo.svg"),
Url = new Uri("https://example.com"),
Telephone = "1234 1243567",
Address = address,
Geo = geo,
SameAs = new Uri("https://example.com"),
OpeningHours = "Mo-Fr 08:00-17:00",
};
var jsonLdlb = localbusiness.ToString();
var organization = new Organization()
{
AreaServed = "US",
ContactPoint = new ContactPoint()
{
AvailableLanguage = "English",
ContactOption = ContactPointOption.TollFree,
ContactType = "customer service",
Telephone = "1234 1243567"
},
Url = new Uri("https://example.com"),
Logo = new Uri("https://example.com/graphics/logo.svg"),
};
var jsonLdorg = organization.ToString();
}
<script type="application/ld+json">
#Html.Raw(jsonLd)
</script>
<script type="application/ld+json">
#Html.Raw(jsonLdlb)
</script>
<script type="application/ld+json">
#Html.Raw(jsonLdorg)
</script>
#using Microsoft.Extensions.Configuration
#inject IConfiguration Configuration
#{
string myValue = Configuration["FrameworkData:PageTitle"];
// Do your things
}
Refer Microsoft Docs
I had to define the variables outside of the problematic code block in an another code block like this:
#{
var pageTitle = Configuration["FrameworkData:PageTitle"];
}
I had to define the altName before initializing the WebSite() instance:
var altName = $"{pageTitle} - {slogan}";
var website = new WebSite()
And then I can just reference the variable by variable name.
Case closed.
I've been searching the whole internet for a guide to help me get a chart working on a Razor Page View from my Asp Net Core project. The thing is, so far I've found stuff using Angular or sites offering a .js for a certain price. Until I stumbled upon a tutorial and put together a code, only to find out that
The type or namespace Chart could not be found
The code looks like this:
public class DashboardController : Controller
{
public IConfiguration Configuration { get; }
public DashboardController(IConfiguration configuration)
{
Configuration = configuration;
}
public ActionResult Index()
{
string query = "SELECT Total_Releases, Completed_Releases FROM ReleaseStats":
string constr = Configuration["ConnectionStrings:DefaultConnection"];
List<ReleaseStatistics> chartData = new List<ReleaseStatistics>();
using (SqlConnection con = new SqlConnection(constr))
{
using (SqlCommand cmd = new SqlCommand(query))
{
cmd.CommandType = CommandType.Text;
cmd.Connection = con;
con.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
while (sdr.Read())
{
chartData.Add(new ReleaseStatistics
{
TotalReleases = Convert.ToInt32(sdr["Total_Releases"]),
CompletedReleases = Convert.ToInt32(sdr["Completed_Releases"])
});
}
}
con.Close();
}
}
return View(chartData);
}
}
#model List<Intersection.Models.Statistics.ReleaseStatistics>
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
#{
var chart = new Chart(width: 500, height: 500, theme: ChartTheme.Yellow)
.AddTitle("Releases")
.AddSeries("Default", chartType: "Pie",
xValue: Model, xField: "Total Releases",
yValues: Model, yFields: "Completed Releases")
.Write();
}
</body>
</html>
Therefore - What should be done so that my View will be able to see the Chart() method? Is there any other way that I could easily add a chart to my view? Any links to guides, tutorials, are very welcome! Although I feel that I already consumed them all.
Chart helper supports asp.net but does not support asp.net core.
If you want to create a pie chart in asp.net core.I suggest that you could try to use Chart.js.
Reference:
https://www.chartjs.org/docs/latest/charts/doughnut.html
https://www.c-sharpcorner.com/article/creating-charts-with-asp-net-core/
Supposedly, when a file included in a bundle is modified, the url key is supposed to be updated, forcing the client to clear its cache.
However, I have found this to be a very unreliable process - for some reason, the URL key is not always changed even when there are many changes to the underlying files.
So what I wanted to do was to use the assembly version in the query string so that whenever a release occurred, all clients would clear their cache so that they would be updated to the most recent version. Here is what I have so far:
Custom transform to modify query variables:
public class VersionBusterTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse Response)
{
string query = string.Format("{0}.{1}.{2}.{3}", Global.Properties.Version.Major, Global.Properties.Version.Minor, Global.Properties.Version.Release, Global.Properties.Version.Build);
Array.ForEach(Response.Files.ToArray(), x => x.IncludedVirtualPath = string.Concat(x.IncludedVirtualPath, "?v=", query));
}
}
Registering the files:
BundleTable.EnableOptimizations = (Global.Properties.Deployment.Environment != DeploymentEnvironment.Development);
var StyleLibrary = new StyleBundle(ConfigBundles.Styles);
StyleLibrary.Include("~/Content/Styles/Libraries/core-{version}.css", new CssRewriteUrlTransform());
StyleLibrary.Include("~/Content/Styles/Libraries/icomoon/style.css", new CssRewriteUrlTransform());
StyleLibrary.Include("~/Content/Styles/Site.css", new CssRewriteUrlTransform());
StyleLibrary.Transforms.Add(new VersionBusterTransform());
BundleTable.Bundles.Add(StyleLibrary);
var ScriptLibrary = new ScriptBundle(ConfigBundles.Scripts);
ScriptLibrary.Include("~/Content/Scripts/Libraries/modernizr-{version}.js");
ScriptLibrary.Include("~/Content/Scripts/Libraries/bootstrap-{version}.js");
ScriptLibrary.Include("~/Content/Scripts/Framework/yeack.js");
ScriptLibrary.Transforms.Add(new JsMinify());
ScriptLibrary.Transforms.Add(new VersionBusterTransform());
BundleTable.Bundles.Add(ScriptLibrary);
Method to get URL:
public static string Query(string Path)
{
if (HttpRuntime.Cache[Path] == null)
{
var absolutePath = HostingEnvironment.MapPath(Path);
HttpRuntime.Cache.Insert(Path, string.Format("{0}?v={1}.{2}.{3}.{4}", Path, Global.Properties.Version.Major, Global.Properties.Version.Minor, Global.Properties.Version.Release, Global.Properties.Version.Build), new CacheDependency(absolutePath));
}
return HttpRuntime.Cache[Path] as string;
}
Header template:
#Styles.Render(ConfigBundles.Styles)
#Scripts.Render(ConfigBundles.Scripts)
Currently, when I open the site in the Development environment, the following references are printed in the header:
<link href="/Content/Styles/Libraries/core-3.1.0.css?v=0.3.5.0" rel="stylesheet">
<link href="/Content/Styles/Libraries/icomoon/style.css?v=0.3.5.0" rel="stylesheet">
<link href="/Content/Styles/Site.css?v=0.3.5.0" rel="stylesheet">
<script src="/Content/Scripts/Libraries/modernizr-2.6.2.js?v=0.3.5.0"></script>
<script src="/Content/Scripts/Libraries/bootstrap-3.0.0.js?v=0.3.5.0"></script>
<script src="/Content/Scripts/Framework/yeack.js?v=0.3.5.0"></script>
However, in Production, it is printing this:
<link href="/Content/Styles/css?v=SmKL_qNLRzByCaBc0zE--HPqJmwlxxsS9p8GL7jtFsc1" rel="stylesheet">
<script src="/Content/Scripts/js?v=YvUa47U8N_htaVmoq5u1VzHyRgEH3quFSUYpjRonpbM1"></script>
Why doesn't my bundling transform have any effect on what is printed in production?
I have an AngularJS application with a .NET MVC/WebAPI backend. I have one MVC action that serves up my main HTML page that loads my AngularJS app. This MVC action loads several application settings from the Web.config as well as the database and returns them to the view as a model. I'm looking for a good way to set those MVC Model values as $provide.constant values in my AngularJS .config method.
MVC Controller method:
public ActionResult Index() {
var model = new IndexViewModel {
Uri1 = GetUri1(),
Uri2 = GetUri2()
//...etc
};
return View(model);
}
My MVC _Layout.cshtml:
#model IndexViewModel
<!doctype html>
<html data-ng-app='myApp'>
<head>
#Styles.Render("~/content/css")
<script type='text/javascript'>
#if (Model != null) //May be null on error page
{
<text>
var modelExists = true;
var uri1 = '#Model.Uri1';
var uri2 = '#Model.Uri2';
</text>
}
else
{
<text>
var modelExists = false;
</text>
}
</script>
</head>
<body>
<!-- Body omitted -->
#Scripts.Render("~/bundles/angular", "~/bundles/app") //Loads angular library and my application
</body>
app.js:
"use strict";
angular.module('myApp', [])
.config(['$provide' '$window', function ($provide, $window) {
if ($window.modelExists){
$provide.constant('const_Uri1', $window.uri1);
$provide.constant('const_URi2', $window.uri2);
}
}]);
This is a vastly simplified version of my code but I think it illustrates my concern. Is there a better or standard way of doing this that I am overlooking? I don't like the code in my _Layout.cshtml because I have many more configuration values.
If you have a bunch of config values and you don't mind an extra network call, one way to do this is to create an MVC view that returns the settings as an Angular constant...
using System.Web.Script.Serialization;
// ...
public ActionResult Settings(string angularModuleName = "myApp")
{
var settings = new
{
uri1 = GetUri1(),
uri2 = GetUri1()
// ...
};
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(settings);
var settingsVm = new SettingsViewModel
{
SettingsJson = json,
AngularModuleName = angularModuleName
};
Response.ContentType = "text/javascript";
return View(settingsVm);
}
In the Razor view...
#model MyApp.SettingsViewModel
#{
Layout = null;
}
(function (app) {
app.constant('settings', #Html.Raw(Model.SettingsJson));
})(angular.module('#Model.AngularModuleName'));
In the pages that need the files, just add a script tag to bring in the constants...
#Scripts.Render("~/bundles/angular", "~/bundles/app") //Loads angular library and my application
<script src="/home/settings?appname=foo"></scripts>
This will return the script...
(function (app) {
app.constant('settings', {
"uri1": "https://uri1",
"uri2": "https://uri2"
});
})(angular.module('foo'));
Now you can inject the settings service anywhere in your Angular code. Nothing is leaked into the global scope.
You can also use this technique to inject the settings directly into a particular HTML view, but I generally prefer to split it out so that it is included only when needed.
I want to insert, update data from the fusion tables.
While selecting from the fusion table all seems to work fine. But during row addition i need to used OAuth 2.0 but unable to find a suitable solution to get the access token and use it during the insert.
A code sample would help a lot.
var fusiondata;
function initialize() {
// Initialize JSONP request
var script = document.createElement('script');
var url = ['https://www.googleapis.com/fusiontables/v1/query?'];
url.push('sql=');
var query = 'insert into 1bPbx7PVJU9NaxgAGKqN2da4g5EbXDybE_UVvlAE (name,luckynumber) values('abc',89)';
var encodedQuery = encodeURIComponent(query);
url.push(encodedQuery);
url.push('&callback=viewData');
url.push('&key=AIzaSyA0FVy-lEr_MPGk1p_lHSrxGZDcxy6wH4o');
script.src = url.join('');
var body = document.getElementsByTagName('body')[0];
body.appendChild(script);
}
function viewData(data) {
// code not required
}
I know most of you are suffering for google auth and inserting and updating fusion table. I am providing the entire code how to use the gauth lib to insert in a simple manner
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Authorization Request</title>
<script src="https://apis.google.com/js/client.js"></script>
<script type="text/javascript">
function auth() {
var config = {
'client_id': '365219651081-7onk7h52kas6cs5m17t1api72ur5tcrh.apps.googleusercontent.com',
'scope': 'https://www.googleapis.com/auth/fusiontables'
};
gapi.auth.authorize(config, function() {
console.log('login complete');
console.log(gapi.auth.getToken());
});
}
function insert_row(){
alert("insert called");
gapi.client.setApiKey('AIzaSyA0FVy-lEr_MPGk1p_lHSrxGZDcxy6wH4o');
var query = "INSERT INTO 1T_qE-o-EtX24VZASFDn6p3mMoPcWQ_GyErJpPIc(Name, Age) VALUES ('Trial', 100)";
gapi.client.load('fusiontables', 'v1', function(){
gapi.client.fusiontables.query.sql({sql:query}).execute(function(response){console.log(response);});
});
}
</script>
</head>
<body>
<button onclick="auth();">Authorize</button>
<p> </p>
<button onclick="insert_row();">Insert Data</button>
</body>
</html>