Blazor gets a Microsoft.Data.SqlClient error - c#

I'm using a clear architecture for a NET7 project with Blazor. In the Persistence layer, I have a function to register the database and all the repositories
public static class PersistenceServiceRegistration
{
public static IServiceCollection AddPersistenceServices(
this IServiceCollection services,
IConfiguration configuration,
string cnnStringName = "LIUContextConnection")
{
var cnnString = configuration.GetConnectionString(cnnStringName);
services.AddDbContext<LIUContext>(options =>
options.UseSqlServer(cnnString)
);
services.AddScoped<IArticleRepository, ArticleRepository>();
return services;
}
}
So, in the Program.cs I can register the persistence like
builder.Services.AddPersistenceServices(builder.Configuration);
I want to be sure that the database is created before the application starts. I added the following code an the end of the Program.cs
var app = builder.Build();
LIUContext dbcontext = app.Services.GetRequiredService<LIUContext>();
dbcontext.Database.EnsureCreated();
await app.RunAsync();
When I run the application, I get an error because
Microsoft.Data.SqlClient is not supported on this platform
I added in the Client project and in the Persistence project the NuGet package but I get the some error.
If I create the database from the Package Manager Console is working.

"is not supported on this platform" is of course the main point.
The platform here is dotnet in the Browser and a lot of APIs are not (cannot be) supported. The SqlClient needs sockets and they are not allowed in the browser. Also, you wouldn't be able to keep the credentials secret.
There is some limited support for SqLite and you have the built-in IndexedDb.
For a full SQL Db, the common approach is to use a WebService (Blazor Wasm Hosted template) to handle the Db access.

Related

When I run my ASP.NET Core MVC solution from another user it does not connect to the SQL Server database

I have created a simple ASP.NET Core MVC application in Visual Studio. I also created a database using EF Core code first approach. The database uses Windows authentication.
The connection string I used was
Server=(local)\\sqlexpress;Database=LibraryEmployeeDB;Trusted_Connection=True;
I am supposed to send this project to my professor but when I tried to run the project from a different user on my computer it doesn’t work because I can’t connect to the database with the other user.
I tried to change to Windows and SQL Server authentication and create a new user and add that to the connection string but that didn’t work either. I would be so grateful for any tips on how to solve this problem so that my professor can run the project as it is supposed to. Thank you!
You should run init command to create the database :
"Update-Database" in the Package manager console in the Visual studio.
Your Connection String shulde be like this:
"ConnectionStrings": {
"ConnStr": "Data Source=(local)\\sqlexpress;Initial Catalog=LibraryEmployeeDB;Integrated Security=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;Trusted_Connection=false;User ID=yourUsername;Password=yourPassword;"
}
and in Startup.cs you have to use this section:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<YourContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnStr")));
//and other things
}

Share Sqlite database between asp.net core project and .net core console project with a third data class library project

I have 3 projects in a vs2019 solution which should use the same sqlite database using Entity Framework Core.
First project: an .net core class library with all database models, database context, database repositories and migrations.
Second project: an .net core console application which needs to scan files, retrieve metadata from inet and seed the database with retrieved content. This is done by referencing the first project and use the repositories to access the database.
Third project: an Asp.net core Webapi which need to accept client requests for retrieving, updating, etc data from the same database. This is also done by referencing the first project and using the repositories. This project is the main project and needs to start the second project when needed.
All projects need to be able to get installed on client machines targeting Windows, MacOS and Linux. And be self hosted. It has to act like a content server simular to Plex, Emby and Calibre.
My problem is how to create the datasource option string to be able to use the same db file and have that file copied to the location of the Asp.net core Webapi project.
I now have this in my dbcontext file at the first project:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
#region DbSets
public DbSet<Authors> Authors { get; set; }
...
...
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseSqlite("Data Source=Chapter.db");
return new ApplicationDbContext(builder.Options);
}
}
And this in my repository class in the first project:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private DbContextOptionsBuilder<ApplicationDbContext> _optionsBuilder;
public ApplicationDbContext _context = null;
public DbSet<T> table = null;
public GenericRepository()
{
_optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>().UseSqlite("Data Source=Chapter.db");
this._context = new ApplicationDbContext(_optionsBuilder.Options);
table = _context.Set<T>();
}
When I use entity framework to create the database from the migrations in the first project, it creates the Chapter.db file in the main folder of the first project.
In my Asp.net Webapi project i have this to use the database:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite("Data Source=Chapter.db"));
Do i need this line of code or can i use the repository classes from the first project?
When I build the solution I have all the projects their dll's in the debug folder of the third project but not the chapter.db database file.
How do you copy the db file automaticly in the correct projects output directories when building for debug and release?
And what do I need to use for the Datasource= connection string to be sure that after people install the application it targets the db file (in windows, linux and macos)?
I have read two answers from simular questions to use:
Var SqlitePath = Path.combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
#"<YourAppName>\Chapter.db");
And:
var builder = new SqliteConnectionStringBuilder(connectionString);
builder.DataSource = Path.GetFullPath( Path.Combine(
AppDomain.CurrentDomain.GetData("DataDirectory") as string ?
AppDomain.CurrentDomain.BaseDirectory, builder.DataSource);
connectionString = builder.ToString();
Are these answers having the same outcome? And am I able to use one of these in the asp.net core project aswell as in the .net core console application?
And does that work cross-platform?
I'm a bit late and I'm assuming that you have already figured this out, but I'm writing this answer since it was a top result to one of my google searches.
The issue at hand is pointing to the correct location of the database.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite("Data Source=Chapter.db"));
This line of code will be creating a database called "Chapter.db" within the project directory of whichever project you are running it in. The solution to the problem was provided at the bottom of your question.
Var SqlitePath = Path.combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
#"<YourAppName>\Chapter.db");
This code will convert the relative path to the database (based on the project directory) to an absolute path (one based on your file system instead, i.e. C:...\Chapter.db or /home/.../Chapter.db). The other blob you provided should do the same thing, but with more lines of code. After reading SqliteConnectionStringBuilder documentation it seems that the later has more functionality allowing for passwords and other fun stuff to be added through code rather than manually put through a string.
Absolute pathing will help out in this scenario as it will allow for you to specify a direct path to the database file you are using. Otherwise, you would need to modify the connection string to use relative pathing for each of your projects which could become complex if they were to ever move to different parts of your file system. It can still be done. For example if both projects are in the same location, appending "../" in front of your database name would suffice in providing the same location to all of your projects.
Using absolute pathing, you could just specify a location like "C:\Chapter.db" and all of your projects would use the database at that location rather than their individual files.

Publishing a shared appsettings file with .net core

I have a .net core solution that contains a database project (class library) and a console application. The database project contains EF Migrations and to do Add-Migration, most methods use a hard-coded connection string in one place or the other.
To avoid hard-coding (and/or duplication) I have created a shared appsettings.json file in the solution root and I use it in my Main method and the class library
In the console application
static void Main(string[] args)
{
var settingPath = Path.GetFullPath(Path.Combine(#"../appsettings.json"));
var builder = new ConfigurationBuilder()
.AddJsonFile(settingPath, false);
var configuration = builder.Build();
var services = new ServiceCollection()
.AddDbContext<MyContext>(options => options.UseSqlServer(configuration["ConnectionStrings:MyDatabase"]))
.BuildServiceProvider();
}
And in the class library to use migrations
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyContext>
{
public MyContext CreateDbContext(string[] args)
{
var settingPath = Path.GetFullPath(Path.Combine(#"../appsettings.json"));
var builder = new ConfigurationBuilder()
.AddJsonFile(settingPath, false);
var configuration = builder.Build();
var optionsBuilder = new DbContextOptionsBuilder<MyContext>()
.UseSqlServer(configuration["ConnectionStrings:MyDatabase"]);
return new MyContext(optionsBuilder.Options);
}
}
This is working well for development purposes when I use dotnet run but when I publish the console application, it doesn't include the appsettings file. Other than running a powershell script as part of dotnet publish, is there any other cleaner way of including this file when the project is published?
IDesignTimeDbContextFactory is exactly for the purpose its name describes. You shouldn't be running migrations against your production database in the first place, and if you do, you should be generating specific migrations for production into your app (instead of the class library) and using your app for the migrations. See the docs on using a separate project for migrations. That, then, negates the need to share your appsettings.json. Just leave the connection string hard-coded in your factory, since it's only for development anyways.
Now, you might have an issue I suppose in a team environment. However, even if you're using something like SQLite, you can use project-relative paths that won't be developer-specific, and with LocalDB, you can use a normal SQL Server connection string to the MSSQLLocalDB instance, which will be same for every developer using Visual Studio. Regardless, even if you do need to specify the connection specifically by developer, at that point it would make more sense to use user secrets, anyways, since you wouldn't want that info be committed to source control. Otherwise, each developer would end up clobbering the other's copy of appsettings.json, and you'd have a mess on your hands.
Long and short, just hard-code the connection string in your factory, or if you can't or won't, use user secrets for the connection string. In either case, you do not need to share appsettings.json.
The way I've done this before is to specify the startup project when you run dotnet ef (with the -s switch - the options are at https://learn.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet#common-options)
It gets messy quickly, and it's probably easiest to write some wrapper scripts for the project that deal with this kind of thing.

Have migrations and a service that needs the database?

How do I build a service that needs access to my database while also using migrations, given that the migrations tool "dotnet ef database update" runs my application BuildWebHost before creating the database? The attempt to configure the service needing the database throws an exception because the database doesn't exist and causes the migration command to fail. The database therefore never gets created.
I'm using asp.net core 2 and EF Core 2.
More specifically, running "dotnet ef database update" with a blank database fails with the following error:
An error occurred while calling method 'BuildWebHost' on class
'Program'.Continuing without the application service provider. Error:
Cannot open database "MyDb" requested by the login. The login failed.
Login failed for user 'MYCOMPUTER\MYNAME'.
This happens because I have built a custom configuration provider backed by my "MyDb" database (with the end goal of binding to it with a custom options class) as per this Microsoft Configuration Tutorial My program.cs looks like:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((builderContext, config) =>
{
var tmpconfig = config.Build();
config.AddMyOptionsConfig(options => options.UseSqlServer(tmpconfig.GetConnectionString("My_Database")));
})
.UseStartup<Startup>()
.Build();
and the AddMyOptionsConfig eventually runs:
public class EFConfigProvider : ConfigurationProvider
{
[...]
// Load config data from EF DB.
public override void Load()
{
var builder = new DbContextOptionsBuilder<MyDbContext>();
OptionsAction(builder);
using (var dbContext = new MyDbContext(builder.Options))
{
// dbContext.Database.EnsureCreated(); // will cause first migration to fail
Data = !dbContext.ConfigurationValue.Any() // throws exception
? CreateAndSaveDefaultValues(dbContext)
: dbContext.ConfigurationValue.ToDictionary(c => c.Id, c => c.Value);
which throws an SQLException when it attempts to access the database because the database hasn't been created yet.
However, if I try to use dbContext.Database.EnsureCreated() then the initial migration fails because the tables already exist. I thought of trying dbContext.Database.Migrate() instead but as a beginner I'm concerned there might be unintended consequences for a production environment. As such I'd prefer to have control over migrations via the command line tools.
Fundamentally, the problem seems to be that "dotnet ef database update" runs the application startup - BuildWebHost - before it creates the database but the custom configuration provider added in BuildWebHost needs the database to already exist.
How do I solve this dilemma?
In a sense, you've created a circular reference. When running migrations on a ASP.NET Core project, the application is initialized to instantiate the DbContext needed to run migrations against. This is due to the fact that DbContext in EF Core now requires a DbContextOptions instance to be injected, as opposed to the old way of doing things in EF, where the connection string name (or the actual full connection string) would be defined directly on the constructor.
Normally, this would work just fine, but, as you've noticed, because the application initialization, itself, requires an already existing database, there's no way to run this before the migrations. As a result, you have two options:
You can attempt to abstract the piece that requires an existing database. That could be as simple as wrapping it in a try-catch and swallowing the exception, or more complex. Since the migration piece doesn't need this particular functionality, it could safely be excluded from the application initialization in that scenario.
You can move your context and entities into a class library and implement an IDesignTimeDbContextFactory to satisfy the ability to construct a DbContext to migrate against. You would then run the migrations against this class library instead of your ASP.NET Core project. That then sidesteps the issue of having to initialize the application in order to do a migration.
BuildWebHost (and Startup.Configure) shouldn't be used for application startup logic. The guidance from the ASP.NET team is to use Program.Main instead.
My solution so far has been to move the entity backing the custom configuration provider into a separate dbContext.
Now the table can be created when needed via MyDbOptionsContext.Database.EnsureCreated() without affecting the migrations, as they are based on the main context (MyDbContext).
The options table no longer participates in migrations but as it's unlikely to change there shouldn't be a problem. The other downside is the need (due to having 2 contexts) to explicitly specify the context for the dotnet ef command line tool.

How should a GRPC Service be hosted?

I have created a GRPC Server in C# using the example given at Link. Now I want to figure out as how should I be hosting this server so that I achieve following:
Should I make this Server a Console application or a a Windows Service. If I make it a windows Service then updating the service will be cumbersome (which is a big negative) and if I make it a console app then updating will simply need shutting down exe. But that comes with the price of closing the same by mistake. Is there any other better way?
With IIS this issue won't b there as I can simply remove the site from LB and stop the website to perform the update but since GRPC won't be a part of IIS, I am not sure what's the way to get this working.
Any references for the better architecture are welcomed.
We can use Microsoft.Extensions.Hosting pacakge to host a .net core console application by using the HostBuilder API to start building gRPC host and setting it up.
In order to run the gRPC service, we first need to start/stop Grpc.Core.Server in a hosted service. A hosted service is basically a piece of code that is run by the host when the host itself is started and the same for when it is stopped. The following code implement a GrpcHostedService to override IHostedService interface:
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Hosting;
namespace Grpc.Host
{
public class GrpcHostedService: IHostedService
{
private Server _server;
public GrpcHostedService(Server server)
{
_server = server;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_server.Start();
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken) => await _server.ShutdownAsync();
}
}
In the Program.cs, use HostBuilder API to start building our grpc host and setting it up:
public class Program
{
public static async Task Main(string[] args)
{
var hostBuilder = new HostBuilder()
// Add configuration, logging, ...
.ConfigureServices((hostContext, services) =>
{
// Better to use Dependency Injection for GreeterImpl
Server server = new Server
{
Services = {Greeter.BindService(new GreeterImpl())},
Ports = {new ServerPort("localhost", 5000, ServerCredentials.Insecure)}
};
services.AddSingleton<Server>(server);
services.AddSingleton<IHostedService, GrpcHostedService>();
});
await hostBuilder.RunConsoleAsync();
}
}
By doing this, the generic host will automatically run StartAsync on our hosted service, which in turn will call StartAsync on the Server instance, essentially start the gRPC server.
When we shut down the host with Control-C, the generic host will automatically call StopAsync on our hosted service, which again will call StopAsync on the Server instance which will do some clean up.
For other configuration in HostBuilder, you can see this blog.
I'm going to add one more option.
With dot net core, you can run this as a Linux Daemon now.
Currently gRPC doesn't support integration with ASP.Net/IIS. You would need to host the server in a console or as a Windows service.
Likely you would want this to be a Windows service to make it easier to keep the server running across reboots or crashes. If you want to easily turn your console application into a Windows service I would recommend using the excellent TopShelf Nuget.
Updating the service can be done as you would a console app.
Stop the Windows service. net stop <service-name}>
Copy the updated assemblies.
Start the Windowsservice net start <service-name>
My company (Shortbar) is building the application server for a hotel management system called HOLMS on gRPC. Our setup is as follows:
HOLMS.Application is a .NET class library (assembly) that does the actual work of the server
HOLMS.Application.ConsoleRunner is a C# console application that hosts HOLMS.Application. The console runner is used by (1) developers for convenience (mentioned in the question) as well as (2) production scenarios running inside a Docker container, where the container runtime (e.g. Amazon ECS) implements job control/scaling. It follows "12 factor app" guidelines, including running itself as a single, standalone, stateless process, fast startup/shutdown, and environment-variable config injection. The system logs to stdout which gets drained however stdout is drained in the prod environment (e.g. Sumo, logstash, etc). This is how our SaaS multi-tenant solution will go into production.
HOLMS.Application.ServiceRunner packages HOLMS.Application into a Windows service, for more traditional, on-premise situations where a customer's IT group will run the service themselves. This package uses the Windows registry for configuration and relies on Windows service job control for startup/shutdown/restarts. It logs to the Windows Event Log.
The ConsoleRunner and ServiceRunner each are only about 200 lines of code; for the most part, they just wrap the Application package, and call into it.
Hope this helps.

Categories