I have the following project structure in my Azure function:
Application.Function
Application.Domain
Application.Infrastructure
This works fine. All the dependencies are resolved without any errors.
However, when I setup Entity Framework in my Infrastructure layer, and I'm trying to run the application, I get the following error:
webjobsbuilderextensions.cs not found
When I remove all the Entity Framework related things and try to run the application, it works again.
In my Startup.cs I have the following code snippet:
builder.Services.AddDomain(connectionString);
Application.Domain.Injections:
public static class Injections
{
public static IServiceCollection AddDomain(this IServiceCollection services, string connectionString)
{
services.AddTransient<ISalesItemService, SalesItemService>();
services.AddPersistence(connectionString)
return services;
}
}
Application.Infrastructure.Injections:
public static class Injections
{
public static IServiceCollection AddPersistence(this IServiceCollection services, string connectionString)
{
services.AddTransient<ISalesItemDataService, SalesItemDataService>();
services.AddDbContext<IOnePlmSubContext, OnePlmSubContext>(
options => options.UseSqlServer(connectionString),
ServiceLifetime.Transient,
ServiceLifetime.Transient);
return services;
}
}
Has anyone else experienced the same issue that I have? Can't I have this kind of layered structure when working with Azure functions?
Summarize the comments for other communities reference:
Just downgrade version of EF Core to 3.1 and re-build the function, then re-rest the funciton, it works.
Related
I am trying to run 'update-database -verbose' in the Package Manager Console but I am getting the following lines at the end of the output: (nothing is being generated in my SQL server)
Using context 'TutoringContext'.
Finding design-time services for provider 'Microsoft.EntityFrameworkCore.SqlServer'...
Using design-time services from provider 'Microsoft.EntityFrameworkCore.SqlServer'.
Finding design-time services referenced by assembly 'LakeTutoringWebsite'...
Finding design-time services referenced by assembly 'LakeTutoringWebsite'...
No referenced design-time services were found.
Finding IDesignTimeServices implementations in assembly 'LakeTutoringWebsite'...
No design-time services were found.
I see that I can create a DesignTimeDbContextFactory like this: https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli
But the constructor must be parameterless so I can't pass an IConfiguration object to get my connection string. How can I run 'update-database' without hard coding my connection string?
Based on the above link, I thought I would be able to run 'update-database' since I have added my DBContext to services.
I am using dependency injection for my DBContext in my project:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddDbContext<TutoringContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("LakeTutoringDatabase")));
}
}
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class TutoringContext : IdentityDbContext<IdentityUser>
{
public DbSet<Comment> Comments { get; set; }
public TutoringContext(DbContextOptions<TutoringContext> options) : base(options)
{
}
}
I am using ASP.NET Core 3.1
I installed System.Configuration.Configuration version 6.0.0 using NuGet.
Firstly, as well as creating an implementation of IDesignTimeDbContextFactory, your project will need to add a reference to package:
Microsoft.EntityFrameworkCore.Design
Normally, hard-coded strings are not so bad in this class because you are just working with a development database. Ideally, you would apply migrations to other databases (staging, beta, production) in code (instead of update-database in command line) at application startup which will use values from your appsettings.json file accessed thru IConfiguration.
However, if you do want to have the development connection string accessed from a json file, this link does a pretty good job of walking you thru it.
My EF-Core database access layer and ASP.NET Core application are in different projects. I am trying to wire-up Database access layer to Web application and come with code
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Connection")));
services.AddScoped<IMyDbContext, DatabaseContext>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IPlanService, PlanService>();
services.AddControllersWithViews();
}
Unfortunately, this adds a dependency on the EntityFrameworkCore library. How to move DI wire-up outside Web application into the Database project?
I was using a similar approach in ASP.NET MVC(not Core) project with Unity DI, there was UnityContainerExtension class for that. My old example
DI wire-up in the database layer
public class SharedUnityRegistry : UnityContainerExtension
{
private readonly Func<Type, LifetimeManager> _lifeTimeManager;
public SharedUnityRegistry(Func<Type, LifetimeManager> lifetimeManager)
{
_lifeTimeManager = lifetimeManager;
}
protected override void Initialize()
{
Container.RegisterTypes(
AllClasses.FromLoadedAssemblies().Where(type => typeof(DatabaseContext).IsAssignableFrom(type)),
WithMappings.FromAllInterfaces,
WithName.Default,
_lifeTimeManager);
}
}
DI Wire-up in ASP.NET web project, no dependency from EF
UnityContainer.AddExtension(new SharedUnityRegistry(lifetimeManager => new HierarchicalLifetimeManager()));
You could use an extension method for this and place it in your database project. But you still need to have a reference to your database project.
Database project:
public static class Extensions
{
public static IServiceCollection UseMyDatabase(this IServiceCollection services, string connectionString)
{
services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(connectionString));
return services;
}
}
Startup.cs:
services.UseMyDatabase(Configuration.GetConnectionString("Connection"));
In this project I've solved this issue like this:
Create interface IStartupRegistrator which is being called during application startup. This allows satellite assemblies to register their services, without having the need to decide in startup itself.
Implement EF data access layer which is NOT aware of the underlying DB
Create a dedicated data context (with migrations) for supported DBs. In my case MsSql, Sqlite and in memory (used for unit tests). The DB to use is being set in the app settings and each DB provider decides if it is him having to register himself; e.g. here
Note that only projects in the namespace Sppd.TeamTuner.Infrastructure.DataAccess reference EF core. For the record: I wasn't able to use asp.net identity, because of this.
I have a scenario where we have a "standardised startup" for many small AspNet Core websites.
A seemingly obvious solution to achieve this is to refactor the Startup.cs class into a separate common assembly (as Infrastructure.Web.Core.Startup). We then have each small AspNet Core website reference it the common assembly and use that startup class instead:
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder CreateWebHostBuilder( string[] args )
{
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices( collection => { } )
.UseContentRoot( System.IO.Directory.GetCurrentDirectory() )
.UseStartup<Infrastructure.Web.Core.Startup>(); //.UseStartup<Startup>();
}
Somehow, this breaks attribute routing in the sense that the routes are not hit. No errors, but not routing. The moment I copy the class back into the website project (with the exact same code) it works again.
As a test, if I wrap the Startup.cs class in the common library in a local startup class (like below), it also works:
public class Startup
{
private readonly Infrastructure.Web.Core.Startup _startup;
public Startup( IConfiguration configuration )
{
_startup = new Infrastructure.Web.Core.Startup( configuration );
}
public IConfiguration Configuration => _startup.Configuration;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services )
{
_startup.ConfigureServices( services );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IHostingEnvironment env )
{
_startup.Configure( app, env );
}
}
If I had to take a guess, it's probably something to do with the Dependency Injection.
Does anyone have any suggestions?
FYI: It's using typical AspNet Core 2.1 projects
UPDATE
Incidentally, if I use inheritance it also works but the derived class must be in the same project as the website. I guess it seems obvious, but thought I include that information for completeness sake:
public class Startup : Infrastructure.Web.Core.Startup
{
public Startup( IConfiguration configuration ) : base(configuration)
{
}
}
You can fix this by adding the following statement to your services in your Startup.cs method.
services.AddApplicationPart(typeof(AnTypeInTheOtherAssembly).Assembly);
This will tell the View/Controller Discovery to also check for the new location. Your Project which contains the Startup.cs file would be the Startup Project, and all the others would be just references and libraries or similar.
As of .Net Core 3 you can use something called Razor Class Libraries, see the MSDN. This will automatically add this your Controllers and Views to the discovery, it also has debugging support and will work just as a normal Class Library would.
I have an Azure Function App with a function that runs on a blob trigger. I've proven that this function can run through the Azure Portal and responds to this blob trigger without issues... or at least it did.
Now that I've added functionality which makes use of EF Core (2.2.4), it gives me the following error, both when debugging locally and when publishing to Azure:
Microsoft.Azure.WebJobs.Host: Error indexing method 'ParseThings'. Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'context' to type AvastusContext. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
I have a Startup class as instructed by Azure Function App documentation here, and have followed their example to the letter, aside from the following line in place of their configured example services:
[assembly: FunctionsStartup(typeof(AvstFunctionApp.Startup))]
namespace AvstFunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddDbContext<AvastusContext>(options => options.UseSqlServer(Environment.GetEnvironmentVariable("AvastusDb")));
}
}
}
And the beginning of my function:
public static class ParseThings
{
[FunctionName("ParseThings")]
public static void Run([BlobTrigger("summaries/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log, AvastusContext context)
I can confirm that the AddDbContext line is getting hit in a debugger, so presumably there's some bug happening behind the scenes here, or I'm doing something incredibly silly.
Things I've tried that haven't worked include:
Adding .BuildServiceProvider(true) to the AddDbContext line
Using WebJobsStartup instead of the more recently advertised FunctionsStartup
Downgrading to .NET Core 2.2.0
Changing the Function class and Run method from static to instance
Fixing incorrect namespace of the injected AvastusContext
It's also worth noting that there are two other functions in this Function App project which don't seem to have any serious issues, and I've been able to get dependency injection working using a similar method with EF Core for another (ASP.NET Core MVC) project in this solution.
Thank you in advance for any help anyone can provide!
P.S. I find it incredibly weird that there hasn't been anything describing this situation with the later versions of .NET Core, Azure Function Apps, and EF Core on the interwebs, which leads me to believe that this might be a simple mistake. Hopefully not.
perhaps one solution can be you can try injecting IServiceProvider in your function instead of AvastusContext like I have injected in the repository class below:
private readonly IServiceProvider serviceProvider;
public SomeRepository(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
using var context = this.serviceProvider.GetService<XYZDBContext>();
This will provide a context object to you. Also, Not sure why you are trying to access context in the function directly for good practice have a context class defined, and maintain repository to do any CRUD operation in the code.
Startup.cs you can add extra configurations like :
builder.Services.AddDbContext<XYZDBContext>(
options =>
{
options.UseSqlServer(
conn,
sqlServerOptionsAction:
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
}, ServiceLifetime.Transient);
this configuration works perfectly fine in my current solution. Try this out.
Function app can't resolve dbcontext in functions as it can only resolve BindingContext. You need to create custom bindings to use dbcontext directly in function app.
Other way to get dbcontext injected via DI is to pass it to constructor and using a class level variable in the function.
public class ParseThings
{
private AvastusContext _context;
public ParseThings(AvastusContext context){
_context = context;
}
[FunctionName("ParseThings")]
public void Run([BlobTrigger("summaries/{name}", Connection = "StorageConnectionString")]Stream myBlob, string name, ILogger log){
// use _context here
}
}
If it still doesn't resolve you might want to look it into whether the functionsStartup is configured properly
I have Lamar set up in my .NET Core 2 project:
public class Program
{
public static void Main(string[] args)
{
IWebHost webhost = CreateWebHostBuilder(args).Build();
//((Container)webhost.Services).GetInstance<IStart>().Run();
webhost.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseLamar()
.UseStartup<Startup>();
}
...
public class Startup
{
...
public void ConfigureContainer(ServiceRegistry services)
{
services.Configure<Configuration.Auth>("auth", Configuration);
...
services.Scan(s =>
{
s.TheCallingAssembly();
s.WithDefaultConventions();
});
services.AddCors();
services.AddMvc()
.AddJsonOptions(o =>
{
o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
o.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<Context>(options => options.UseSqlServer(Configuration.GetConnectionString("defaultConnection")));
}
}
However, when attempting to use Scaffold API Controller with actions, using Entity Framework I run into the following error:
There was an error running the selected code generator: 'No parameterless constructor defined for this object.'
Looking up this https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/?view=aspnetcore-2.2#update-main-method-in-programcs suggested that this can show up in ASP.NET Core 2 projects that attempt to still use the .NET 1.x structure.
I found a hacky work-around that I'll post below, which suggests that the scaffolding code generation may have an issue with Lamar. However, is there a better solution? Can you set up Lamar to be able to handle Entity Framework Code generation?
Considering EF was failing in the generate code section, I wondered if perhaps the issue was not the parameterless constructor (I'm pretty sure that whatever unnamed object it was referring to actually has one) but the issue with how the WebHost gets built when using Lamar.
The note in the Lamar documentation on integrating with ASP.NET Core states
Note! The Startup.ConfigureServices(ServiceRegistry) convention does not work as of ASP.Net Core 2.1. Use ConfigureContainer(ServiceRegistry) instead.
I was using that Lamar function in my Startup; however, if I changed it back to ConfigureContainer(IServiceCollection services) (and commented out the Lamar-specific functions, such as Scan), I found that I was able to scaffold the EF controller again.
So, at the moment, my workaround is to comment out Lamar before scaffolding, and then uncomment it back once I'm done. I suspect there may be a better solution though...