53

I know this seems like a simple thing but I can't find any help online.

I want to include a file (.html) along with my Azure function when I publish it using Visual Studio. Then I want to be able to access this file in my Azure function. Why? It seems like only the .dll gets sent to the server when I publish.

This file will be an .html file that will be an email template. I want to read it in my function and then send emails out.

Any help is much appreciated.

I see I can use [send grid in Azure functions][1], but it looks like I can only send out one email and not multiple emails, which is what I want.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
chuckd
  • 13,460
  • 29
  • 152
  • 331
  • 1
    It would be more idiomatic to put html file to blob storage – Mikhail Shilkov Oct 03 '17 at 06:02
  • Can I publish it with the function and then access it in the function in Visual Studio??? – chuckd Oct 03 '17 at 06:04
  • Have your email template as a method inside the function itself and not as a separate file. – Thennarasan Oct 03 '17 at 06:09
  • At least you can always create this file manually via Portal(View files -> Add) and access it by following path: `D:\home\site\wwwroot\NameOfFunction\FileName.html` – Slava Utesinov Oct 03 '17 at 06:10
  • You can use blob input binding to access it. Upload has usual options, nothing specific to functions. – Mikhail Shilkov Oct 03 '17 at 06:10
  • Thennarasan - I'd inlcude the email as a method if it was short, buts it's very long. If you know a elegant way to do this I'm all ears. – chuckd Oct 03 '17 at 06:12
  • Mikhail - "Upload has usual options, nothing specific to functions" what do you mean by this?? – chuckd Oct 03 '17 at 06:14
  • Slava - I'm new to Azure, do you know if I can include and add this file when I publish from Visual Studio? And when I'm debugging on my local machine is there a way to access the file? – chuckd Oct 03 '17 at 06:24

2 Answers2

85

First, you need to add the html file to your project, and in the properties, set Copy to Output Directory to "Copy if newer".

Then in your function code, take in an additional ExecutionContext context parameter (note that this is Microsoft.Azure.WebJobs.ExecutionContext and not System.Threading.ExecutionContext). And when you need to access your html file, you can then write:

string htmlFilePath = Path.Combine(context.FunctionAppDirectory, "test.html");

That's assuming you added the file at the root of your VS project. If you instead added it in some Data folder (better practice), you'd write:

string htmlFilePath = Path.Combine(context.FunctionAppDirectory, "Data", "test.html");

See here for full working sample.

David Ebbo
  • 42,443
  • 8
  • 103
  • 117
  • @user1186050 I'm not seeing any picture in your post. So you set *Copy if newer* and that doesn't copy it to the output folder? Does it work if you use my test repo https://github.com/davidebbo-test/FunctionAppVS2017, for comparison? – David Ebbo Oct 03 '17 at 19:02
  • 1
    It works now, I had to make some other changes. Thanks – chuckd Oct 03 '17 at 19:53
  • 3
    Amazing answer, spent hours looking for this solution. – M0rty Sep 18 '18 at 09:23
  • 8
    Helpful answer thanks. A couple of potentially useful remarks if your helper function sits outside the main Azure Function class: You want type `Microsoft.Azure.WebJobs.ExecutionContext` and not `System.Threading.ExecutionContext`. Also, if you haven't already got `context` it in your Azure Function definition, do it like so: `public static async Task Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log, ExecutionContext context)` – golfalot Oct 31 '18 at 16:35
  • 2
    A link to the documentation https://github.com/Azure/azure-functions-host/wiki/Retrieving-information-about-the-currently-running-function – golfalot Oct 31 '18 at 16:38
  • 3
    fyi - i was doing dependency injection inside my IWebJobsStartup class (which doesn't have access to ExecutionContext AFAIK) and wanted to load custom json configuration so the easiest way to obtain a working local path for me was by manipulating Assembly.GetExecutingAssembly().Location – alv Nov 01 '18 at 02:30
  • In my case (website_run_from_package=1) the approach led me to the folder of the particular function which was invoked. To get to the root, I had to do Path.Combine(context.FunctionAppDirectory, "..") – mJay Jan 23 '20 at 15:13
  • Works for me too! Amazing, thanks – Sara Briccoli Sep 29 '21 at 10:47
3

I have the same scenario as you have. However, I cannot access ExecutionContext because it is only available in requests. My scenario needs to get the template included in AzFunc project but not in the context of AzFunc's functions. I got it null when I go with the interface - implementation class approach.
Thanks to this guy, I use IOptions<ExecutionContextOptions> in my class to get the root directory of the Azure Func.

My Azure Func project (NET 6, Azure Function v4)

using Microsoft.Extensions.Options;
using Microsoft.Azure.WebJobs.Host.Bindings;
namespace AzureFuncApi
{
    public class TemplateHelper : ITemplateHelper
    {
        private readonly IOptions<ExecutionContextOptions> _executionContext;
        public TemplateHelper (IOptions<ExecutionContextOptions> executionContext)
        {
            _executionContext = executionContext;
        }
        public string GetTemplate()
        {
            var context = _executionContext.Value;
            var rootDir = context.AppDirectory; // <-- rootDir of AzFunc
            var template = Path.Combine(rootDir, "test.html"); // <-- browse for your template. Here's an example if you place test.html right in the root of your project
            // return your template here, raw, or after you do whatever you want with it...
        }
    }
}

My different project defines the interface and uses it there, independently of the real implementation

namespace DifferentProject
{
    public interface ITemplateHelper
    {
        string GetTemplate(); // Use this to get the template
    }
}
Lam Le
  • 1,489
  • 3
  • 14
  • 31