When you add HttpClient using DI in Azure Functions it seems that anything that has a dependency on it must be a singleton, or the HttpClient will get disposed once the lifetime of the dependent class ends.
I'm adding HttpClient w/default settings:
builder.Services.AddHttpClient();
Here's the error I see in the logs when I attempt to re-run the function:
Cannot access a disposed object.
Object name: 'System.Net.Http.HttpClient'.
Can anyone confirm? If so, is this expected behavior?
I am not sure if you had checked the official doc but here's how you should inject the http client factory into your code:
//registering
using System;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton((s) => {
return new MyService();
});
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
}
}
}
//azure function
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace MyNamespace
{
public class HttpTrigger
{
private readonly IMyService _service;
private readonly HttpClient _client;
public HttpTrigger(IMyService service, IHttpClientFactory httpClientFactory)
{
_service = service;
_client = httpClientFactory.CreateClient();
}
[FunctionName("GetPosts")]
public async Task<IActionResult> Get(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "posts")] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var res = await _client.GetAsync("https://microsoft.com");
await _service.AddResponse(res);
return new OkResult();
}
}
}
https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection
Related
I have a Function App that I’m trying to using DI in a class for the logger so that I don’t have to pass the ILogger to every function. I’ve been doing some researching and found where I’m supposed to use ILogger in the class constructor. I’ve done all of the changes I believe are correct but it’s still not logging anything from that class.
I’ve added the logging to my Startup class, updated the hosts.json file, and used ILogger in the constructor. There are no errors but nothing gets logged. Does anyone see something I’m missing?
Startup.cs
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging();
builder.Services.AddTransient<IDataManager, DataManager>();
}
Host.json
{
"version": "2.0",
"logging": {
"fileLoggingMode": "always",
"logLevel": {
"default": "Debug"
},
"applicationInsights": {
"samplingSettings": {
"isEnabled": false,
"excludedTypes": "Request"
}
}
}
}
DataManager.cs
public class DataManager : IDataManager
{
private DataContext _db;
private readonly ILogger<DataManager> _log;
public DataManager(ILogger<DataManager> log, DataContext dataContext)
{
_db = dataContext;
_log = log;
}
}
I have reproduced in my environment and got expected results as below and the code taken from #nareshnagpal06 's GitHub repository:
Startup.cs:
using Microsoft.Azure.Functions.Extensions.DependencyInjection; // install nuget - "Microsoft.Azure.Functions.Extensions"
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;
[assembly: FunctionsStartup(typeof(FunctionApp50.Startup))]
namespace FunctionApp50
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IHelperClass, HelperClass>();
}
}
}
HelperClass.cs:
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;
namespace FunctionApp50
{
public class HelperClass : IHelperClass
{
private static ILogger<IHelperClass> _logger;
public HelperClass(ILogger<IHelperClass> logger)
{
_logger = logger;
}
public void Dowork()
{
_logger.LogInformation("Dowork: Execution Started");
/* rest of the functionality below
.....
.....
*/
_logger.LogInformation("Dowork: Execution Completed");
}
}
}
IHelperClass.cs:
using System;
using System.Collections.Generic;
using System.Text;
namespace FunctionApp50
{
public interface IHelperClass
{
void Dowork();
}
}
Function1.cs:
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
namespace FunctionApp50
{
public class Function1 // Ensure class is not static (which comes by default)
{
private IHelperClass _helper;
public Function1(IHelperClass helper)
{
_helper = helper;
}
[FunctionName("Function1")]
public void Run([TimerTrigger("0 */1 * * * *")] TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
// call helper
_helper.Dowork();
}
}
}
host.json:
{
"version": "2.0",
"logging": {
"fileLoggingMode": "always",
"applicationInsights": {
"samplingExcludedTypes": "Request",
"samplingSettings": {
"isEnabled": true
}
},
"logLevel": {
"Function": "Information"
}
}
}
Terminal Output:
File Output:
Firstly go to run, then paste %temp%\LogFiles\Application\Functions.,then click on Host file in the location then click on log file then the output is:
I use Azure Functions v2
I try to create Startup file:
[assembly: FunctionsStartup(typeof(AzureAppDomainRegistration.Startup))]
namespace AzureAppDomainRegistration
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var config = new ConfigurationBuilder()
.AddJsonFile("config.json")
.Build();
var connString = config["ConnectionStrings:DataContext"];
builder.Services.AddDbContext<DataContext>(options => options
.UseLazyLoadingProxies()
.UseSqlServer(connString));
builder.Services.AddTransient<IActionsRegistrationInfo, EfActionsRegistrationInfo>();
}
}
}
File "config.json" exists and has option Always to copy:
<None Update="config.json">
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
But when I start Azure Function (locally or publish to Azure) I get an error:
Microsoft.Extensions.Configuration.FileExtensions: The configuration
file 'config.json' was not found and is not optional.
what is wrong?
Before .AddJsonFile() method, add this line of code: .SetBasePath(Environment.CurrentDirectory). Then in your project, right click "config.json" -> select properties -> then set "copy to output directory" to "copy always".
And note that I'm using IWebJobsStartup instead of FunctionsStartup.
Here is the code works for me:
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
//using WebJobsStartup instead of FunctionsStartup
[assembly: WebJobsStartup(typeof(FunctionApp20.Startup))]
namespace FunctionApp20
{
public static class Function1
{
[FunctionName("Function1")]
public static void Run([BlobTrigger("samples-workitems/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, ILogger log)
{
log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
}
}
//using IWebJobsStartup instead of FunctionsStartup
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var config = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("config.json",true,true)
.Build();
}
}
}
I'm trying to add a file to a mail sent by an azure function.
Here my function :
#r "Newtonsoft.Json"
#r "SendGrid"
#r "System.Web"
using System.Web;
using SendGrid.Helpers.Mail;
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.Azure.WebJobs.Host;
using System;
public static IActionResult Run(HttpRequest req, TraceWriter log, out SendGridMessage message)
{
log.Info("C# HTTP trigger function processed a request.");
string requestBody = new StreamReader(req.Body).ReadToEnd();
EmailContent data = JsonConvert.DeserializeObject<EmailContent>(requestBody);
if (data == null)
{
throw new ArgumentNullException("Data could not be null");
}
message = new SendGridMessage();
message.AddTo(data.Email);
message.SetFrom(new EmailAddress("no-reply#netflio.com"));
message.AddContent("text/html", HttpUtility.HtmlDecode(data.Body));
message.AddAttachment(data.AttachmentName, Convert.ToBase64String(data.Attachment));
message.SetSubject(data.Subject);
return (ActionResult)new OkObjectResult("OK");
}
public class EmailContent
{
public string Email { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public byte[] Attachment { get; set; }
public string AttachmentName { get; set; }
}
My function works very well in my local machine but not on my azure function.
The file is missing...
I test it and it works well both in locally and on azure. You could refer to the steps as below:
1.Create a HttpTrigger on portal and configure Outputs.
2.Add sendgrid function in HttpTrigger. As you have set up the Outputs, ToAddress, FromAddress, MessageText and MessageSubject all have contain.
3.The output.
not sure if I am missing something here. I am using the AppHostHttpListenerBase in a unit test to test a service and in its constructor I pass "api" for the handlerPath parameter. I have a service registered at /hello/{Name} and am using version 3.9.17 of servicestack.
Within the Config method of my appHost class if I access
EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath
it retrurns "api"
Once I am back in the unit test the same call returns null
If I try and call the service with /hello/test it works.
If I use /api/hello/test it fails
It appears that the AppHostHttpListenerBase is loosing the handlerPath ?
Does this sound like a bug or am I missing something ?
below is the code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
namespace Bm.Tests
{
/// <summary>
/// Test self hosting for unit tests
/// </summary>
[TestFixture]
public class TestService
{
private TestServiceAppHost _apphost;
private const string HOST_URL = #"http://localhost:1337/";
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_apphost = new TestServiceAppHost();
_apphost.Init();
_apphost.Start(HOST_URL);
}
[Test]
public void TestHelloServiceJson()
{
var prefix = EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath;
Assert.AreEqual("api", prefix, "Should be api");
var client = new JsonServiceClient(HOST_URL);
var response = client.Send<HelloResponseTest>(new HelloTest() { Name = "Todd" });
Assert.AreEqual("Hello, Todd", response.Result);
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
_apphost.Stop();
_apphost.Dispose();
}
}
public class HelloTest
{
public string Name { get; set; }
}
public class HelloResponseTest
{
public string Result { get; set; }
}
public class HelloServiceTest : ServiceBase<HelloTest>
{
protected override object Run(HelloTest request)
{
return new HelloResponseTest { Result = "Hello, " + request.Name };
}
}
//Define the Web Services AppHost
public class TestServiceAppHost : AppHostHttpListenerBase
{
public TestServiceAppHost() : base("testing HttpListener", "api", typeof(HelloServiceTest).Assembly) { }
public override void Configure(Funq.Container container)
{
// this works and returns api
var prefix = EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath;
Routes
.Add<HelloTest>("/hello")
.Add<HelloTest>("/hello/{Name}");
}
}
}
If you want the handler root path to be /api you need to add that to the listener url, i.e:
_apphost.Start("http://localhost:1337/api/");
I'm creating a self-hosted REST service using service stack & AppHostHttpListenerBase. I'd like to use a base URI for my services (e.g. "api") like so:
http://myserver/api/service1/param
http://myserver/api/service2/param
How do I do this without defining "api" in each of my routes. In IIS, I can set a virtual directory to isolate the services, but how do I do this when self-hosting?
Here ya go.. (as a bonus this is how you put your service into a plugin.
using BlogEngineService;
using ServiceStack.WebHost.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BlogEngineWinService
{
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Self Host Service", typeof(AppHost).Assembly) { }
public override void Configure(Funq.Container container)
{
Plugins.Add(new BlogEngine());
}
}
}
This is how you autowire it up
The call appHost.Routes.AddFromAssembly2(typeof(HelloService).Assembly); Is what calls the extension to auto wire.
using ServiceStack.WebHost.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.ServiceInterface;
namespace BlogEngineService
{
public class BlogEngine : IPlugin, IPreInitPlugin
{
public void Register(IAppHost appHost)
{
appHost.RegisterService<HelloService>();
appHost.Routes.AddFromAssembly2(typeof(HelloService).Assembly);
}
public void Configure(IAppHost appHost)
{
}
}
}
This is how you mark the Service Class to give it a prefix.
Simply mark the class with this attribute
using ServiceStack.DataAnnotations;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BlogEngineService
{
public class Hello
{
[PrimaryKey]
public string Bob { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
[PrefixedRoute("/test")]
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Bob};
}
}
}
Create a CS file in your project for the extension..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using ServiceStack.Common;
using ServiceStack.Common.Utils;
using ServiceStack.Common.Web;
using ServiceStack.Text;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints;
using ServiceStack.ServiceInterface;
namespace ServiceStack.ServiceInterface
{
public static class ServiceRoutesExtensions
{
/// <summary>
/// Scans the supplied Assemblies to infer REST paths and HTTP verbs.
/// </summary>
///<param name="routes">The <see cref="IServiceRoutes"/> instance.</param>
///<param name="assembliesWithServices">
/// The assemblies with REST services.
/// </param>
/// <returns>The same <see cref="IServiceRoutes"/> instance;
/// never <see langword="null"/>.</returns>
public static IServiceRoutes AddFromAssembly2(this IServiceRoutes routes,
params Assembly[] assembliesWithServices)
{
foreach (Assembly assembly in assembliesWithServices)
{
AddNewApiRoutes(routes, assembly);
}
return routes;
}
private static void AddNewApiRoutes(IServiceRoutes routes, Assembly assembly)
{
var services = assembly.GetExportedTypes()
.Where(t => !t.IsAbstract
&& t.HasInterface(typeof(IService)));
foreach (Type service in services)
{
var allServiceActions = service.GetActions();
foreach (var requestDtoActions in allServiceActions.GroupBy(x => x.GetParameters()[0].ParameterType))
{
var requestType = requestDtoActions.Key;
var hasWildcard = requestDtoActions.Any(x => x.Name.EqualsIgnoreCase(ActionContext.AnyAction));
string allowedVerbs = null; //null == All Routes
if (!hasWildcard)
{
var allowedMethods = new List<string>();
foreach (var action in requestDtoActions)
{
allowedMethods.Add(action.Name.ToUpper());
}
if (allowedMethods.Count == 0) continue;
allowedVerbs = string.Join(" ", allowedMethods.ToArray());
}
if (service.HasAttribute<PrefixedRouteAttribute>())
{
string prefix = "";
PrefixedRouteAttribute a = (PrefixedRouteAttribute)Attribute.GetCustomAttribute(service, typeof(PrefixedRouteAttribute));
if (a.HasPrefix())
{
prefix = a.GetPrefix();
}
routes.AddRoute(requestType, allowedVerbs, prefix);
}
else
{
routes.AddRoute(requestType, allowedVerbs);
}
}
}
}
private static void AddRoute(this IServiceRoutes routes, Type requestType, string allowedVerbs, string prefix = "")
{
var newRoutes = new ServiceStack.ServiceHost.ServiceRoutes();
foreach (var strategy in EndpointHost.Config.RouteNamingConventions)
{
strategy(newRoutes, requestType, allowedVerbs);
}
foreach (var item in newRoutes.RestPaths)
{
string path = item.Path;
if (!string.IsNullOrWhiteSpace(prefix))
{
path = prefix + path;
}
routes.Add(requestType, restPath: path, verbs: allowedVerbs);
}
}
}
public class PrefixedRouteAttribute : Attribute
{
private string _prefix { get; set; }
private bool _hasPrefix { get; set; }
public PrefixedRouteAttribute(string path)
{
if (!string.IsNullOrWhiteSpace(path))
{
this._hasPrefix = true;
this._prefix = path;
//this.Path = string.Format("/{0}{1}", Prefix, Path);
}
}
public bool HasPrefix()
{
return this._hasPrefix;
}
public string GetPrefix()
{
return this._prefix;
}
}
}
ServiceStack's HttpListener hosts expects to be hosted a the root / path as the normal use-case is to have each self-hosted service available on different custom ports.
Since it doesn't currently support hosting at a /custompath, you would have to specify /api/ prefix on all your service routes.
Add an issue if you want to see support for hosting at custom paths.
There is actually an easier solution. In your web.config, update your http-handler to:
<httpHandlers>
<add path="api*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" />
</httpHandlers>
With the above, all of your service apis must be prefixed with a "/api/". If you have already used "/api/" in any of your routes, you must now remove them or have to specify it twice in your calls.
Reference:
https://github.com/ServiceStack/SocialBootstrapApi
I've found a workaround for this. I've only tested this under self hosting.
Create a 'PrefixedRouteAttribute' class that inherits from RouteAttribute
public class PrefixedRouteAttribute : RouteAttribute
{
public static string Prefix { get; set; }
public PrefixedRouteAttribute(string path) :
base(path)
{
SetPrefix();
}
public PrefixedRouteAttribute(string path, string verbs)
: base(path, verbs)
{
SetPrefix();
}
private void SetPrefix()
{
if (!string.IsNullOrWhiteSpace(Prefix))
{
this.Path = string.Format("/{0}{1}", Prefix, Path);
}
}
}
When you create your AppHost you can set your Prefix
PrefixedRouteAttribute.Prefix = "api";
Then instead of using the [Route] attribute, use the [PrefixRoute] attribute on your classes
[PrefixedRoute("/echo")]
[PrefixedRoute("/echo/{Value*}")]
public class Echo
{
[DataMember]
public string Value { get; set; }
}
This will then work for requests to
/api/echo
/api/echo/1
This could possibly be improved. I don't really like the how I need to set the Prefix via the static property but I couldn't think of a better approach under my setup. The principle of creating the overriding attribute seems sound though, and that is the important part.