Package and publish Azure Cloud Service with unique Sql Azure connectionStrings - sql

I have a .NET Web solution with an Azure Cloud Service project and a single webrole. I deploy it to the East coast West coast data/compute centers for failover purposes and have been asked to automate the deployment using Powershell MSBuild and Jenkins.
The problem is i need to change the Sql Azure database connectionString in the Web.config prior to packaging and publishing to each deployment. Seems simple enough.
I understand that the webrole properties Settings tab allows you to add custom configuration properties to each deployment with a type of either "string" or "Connection String" but it looks like the "Connection String" option applies to only Blob, Table or Queue storage. If I use the "String" and give it an Sql Azure connection string type it writes it out as an key/value pair and Entity Framework and the Membership Provider do not find it.
Is there a way to add a per-deployment connection string setting that points to Sql Azure?
Thanks,
David

Erick's solution is completely valid and I found an additional way to solve the problem so I thought I'd come back and put it up here since I had such trouble finding an answer.
The trick is getting the Entity Framework and any providers like asp.net Membership/profile/session etc... to read the connection string directly from the Azure service configuration rather than the sites web.config file.
For the providers I was able to create classes that inherit the System.Web.Providers.DefaultMembershipProvider class and override the Initialize() method where I then used a helper class I wrote to retrieve the connection string using the RoleEnvironment.GetConfigurationSettingValue(settingName); call, which reads from the Azure service config.
I then tell the Membership provider to use my class rather than the DefaultMembershipProvider. Here is the code:
Web.config:
<membership defaultProvider="AzureMembershipProvider">
<providers>
<add name="AzureMembershipProvider" type="clientspace.ServiceConfig.AzureMembershipProvider" connectionStringName="ClientspaceDbContext" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
Note the custom provider "AzuremembershipProvider"
AzuremembershipProvider class:
public class AzureMembershipProvider : System.Web.Providers.DefaultMembershipProvider
{
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
string connectionStringName = config["connectionStringName"];
AzureProvidersHelper.UpdateConnectionString(connectionStringName, AzureProvidersHelper.GetRoleEnvironmentSetting(connectionStringName),
AzureProvidersHelper.GetRoleEnvironmentSetting(connectionStringName + "ProviderName"));
base.Initialize(name, config);
}
}
And here's the helper class AzureProvidersHelper.cs:
public static class AzureProvidersHelper
{
internal static string GetRoleEnvironmentSetting(string settingName)
{
try
{
return RoleEnvironment.GetConfigurationSettingValue(settingName);
}
catch
{
throw new ConfigurationErrorsException(String.Format("Unable to find setting in ServiceConfiguration.cscfg: {0}", settingName));
}
}
private static void SetConnectionStringsReadOnly(bool isReadOnly)
{
var fieldInfo = typeof (ConfigurationElementCollection).GetField("bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
if (
fieldInfo != null)
fieldInfo.SetValue(ConfigurationManager.ConnectionStrings, isReadOnly);
}
private static readonly object ConnectionStringLock = new object();
internal static void UpdateConnectionString(string name, string connectionString, string providerName)
{
SetConnectionStringsReadOnly(false);
lock (ConnectionStringLock)
{
ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings["name"];
if (connectionStringSettings != null)
{
connectionStringSettings.ConnectionString = connectionString;
connectionStringSettings.ProviderName = providerName;
}
else
{
ConfigurationManager.ConnectionStrings.Add(new ConnectionStringSettings(name, connectionString, providerName));
}
}
SetConnectionStringsReadOnly(true);
}
}
The key here is that the RoleEnvironment.GetConfigurationSettingValue reads from the Azure service configuration and not the web.config.
For the Entity Framework that does not specify a provider I had to add this call to the Global.asax once again using the GetRoleEnvironmentSetting() method from the helper class:
var connString = AzureProvidersHelper.GetRoleEnvironmentSetting("ClientspaceDbContext");
Database.DefaultConnectionFactory = new SqlConnectionFactory(connString);
The nice thing about this solution is that you do not end up having to deal with the Azure role onstart event.
Enjoy
dnash

David,
A good option is to use the Azure configurations. If you right click on the Azure project, you can add an additional configuration. Put your connection string(s) in the correct configuration (e.g., ServiceConfiguration.WestCoast.cscfg, ServiceConfiguration.EastCoast.cscfg, etc).
In your build script, pass the TargetProfile property to MSBuild with the name of the configuration, and those settings will be built into the final cscfg.
Let me know if you run into any problems. I did the approach, and it took a few tries to get it working right. Some details that might help.
Erick

Related

"No database provider has been configured for this DbContext"

I'm getting this error message when trying to reach my ASP .NET Core 3.1 Web API with Postman:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
However, I do have configured it using AddDbContext in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
..
string connectionString = Configuration.GetConnectionString("ViewQlikDatabase");
services.AddDbContext<QlikDbContext>(options => options.UseSqlServer(connectionString));
}
I have checked the connection string and it is correctly retrieved.
The DbContext also has the recommended constructor:
public QlikDbContext(DbContextOptions<QlikDbContext> options)
: base(options)
{
}
The exception is raised when I try to call the context in my business class:
public string SedeGetTotaleElementiVista()
{
using (var db = new QlikDbContext())
{
// Exception raised here
int count = db.ViewQlikSede.Count();
return count.ToString();
}
}
Can someone please tell me what's wrong?
The context must be injected. If you new it up yourself, the service registration doesn't come into play at all. Here, you're creating it yourself, and not passing anything into it, so this instance definitely doesn't have a provider configured.

Entity Framework ConnectionString Not Working

From as far as I can tell all of the questions that relate to this have not solved my issue.
I have an sql database hosted by Azure. Super simple, I want to connect it to my ASP.Net MVC app using Entity Framework.
Data Model has been added and edmx file is present,
Connection string
<connectionStrings><add name="MyDatabase" connectionString="...serverinfo"
providerName="System.Data.EntityClient" /></connectionStrings>
I go to my IdentityModels.cs and change the following
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MyDatabase", throwIfV1Schema: false)
{
}
When i go to register a user in hopes that the users information will be added to my Azure database i get the following; "The entity type ApplicationUser is not part of the model for the current context."
I am kind of at my end on this. If someone can point me to documentation to build and applicaiton based upon a previously created database that I can follow or know what I am missing please let me know. Thank you
Edit your Connection String and Modify your code as Follow -
<connectionStrings>
<add name="MyDatabasedbEntitiesapplication" providerName="System.Data.SqlClient" connectionString="database and username password;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" />
<add name="MyDatabasedbEntities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=yourdatasource,1433;initial catalog=yourdbname;user id=username;password=***;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
Modify the code:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MyDatabase", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
Modify AutomaticMigrationsEnabled = true; in Configuration class under Migrations folder.

Asp.Net core dbcontext issue

I am creating Asp.net core application and trying to connect to the database. I am getting an error at the following line of code in ConfigureServices method in the startup.cs file. The error that I am getting is the value cannot be null. It seems like it cant find the CustomerOrderEntities key in the web.config file. Not sure what the problem is
services.AddDbContext<CustomerOrderEntities>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CustomerOrderEntities")));
Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CustomerOrderEntities>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CustomerOrderEntities")));
AutoMapperConfiguration.Configure();
// Create the container builder.
var builder = new ContainerBuilder();
// Register dependencies, populate the services from
// the collection, and build the container. If you want
// to dispose of the container at the end of the app,
// be sure to keep a reference to it as a property or field.
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<DbFactory>().As<IDbFactory>();
builder.RegisterType<CustomerRepository>().As<ICustomerRepository>();
builder.RegisterType<ProductRepository>().As<IProductRepository>();
builder.RegisterType<OrderRepository>().As<IOrderRepository>();
builder.Populate(services);
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
}
Web.Config
<connectionStrings>
<add name="CustomerOrderEntities" connectionString="metadata=res://*/EF.CustomerOrderContext.csdl|res://*/EF.CustomerOrderContext.ssdl|res://*/EF.CustomerOrderContext.msl;provider=System.Data.SqlClient;provider connection string="data source=test-PC\MSSQLSERVER2014;initial catalog=CodeFirstTest;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
Connection string in ASP.NET Core is defined in appsettings.json, for example:
"ConnectionStrings": {
"CustomerOrderEntities": "Server=(localdb)\\mssqllocaldb;Database=aspnet-a3e892a5-6a9c-4090-bc79-fe8c79e1eb26;Trusted_Connection=True;MultipleActiveResultSets=true"
},
In your case name of connecting string is CustomerOrderEntities, you get null, probably it's not there, check you appsettings.json.
Your connectionstring in Appsettings.json is definitely missing. You cannot set connection string in web.config in Asp.net core whether you are targeting full .net framework or .net core. Alternative you type the connection string value in services.AddDbContext(options =>
options.UseSqlServer("Your Connectionstring"));
for more information check here;
https://learn.microsoft.com/en-us/ef/core/

Custom Authorization & Role in MVC 4 not working

I need to implement a Single Sign on where a Com+ component should be called to authenticate the user & provide the roles. In short, I need to bypass the default mechanism in MVC 4 where it tries to access the aspnetdb database. So I started with a new MVC4 internet project and added the following code.
In Global.asax
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
bool retval = CreateUserObject("John", "pwd");
}
private bool CreateUserObject(string userName, string password)
{
string[] currentUserRoles = { "Admin", "User" };
GenericPrincipal userPrincipal = new GenericPrincipal(new GenericIdentity(userName), currentUserRoles);
HttpContext.Current.User = userPrincipal;
//Thread.CurrentPrincipal = userPrincipal;
return true;
}
Within the HomeController.cs, I added the [Authorize] attribute for the "About" action as below and it works as expected
[Authorize]
public ActionResult About()
However if I modify the [Authorize] attribute to permit only "Admin" role as below I get a runtime error (at the bottom). Is there a way around this to use my own collection of roles for the logged in user, instead of querying the database? I also need to do something similar to the user Profile as well (i.e, instead of database, I should populate the values from the Com+ application.
[Authorize(Roles = "Admin")]
public ActionResult About()
Server Error in '/' Application.
A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)
Maybe you need to create a Custom RoleProvider, like this:
namespace DemoApp.Providers
{
public class MyCustomRoleProvider : System.Web.Security.SqlRoleProvider
{
public override string[] GetRolesForUser(string username)
{
string[] currentUserRoles = { "Admin", "User" };
return currentUserRoles;
}
}
}
And in the web.config of the application, change the default role provider:
<system.web>
<roleManager enabled="true" defaultProvider="DefaultRoleProvider">
<providers>
<add name="DefaultRoleProvider" type="DemoApp.Providers.MyCustomRoleProvider, DemoApp"/>
</providers>
</roleManager>
<system.web>

Adding forms authentication (SimpleMembership) to webapi app

I'm trying to add to a MVC4 webapi project the simple membership provider authentication mechanism found in a MVC 4 web application project, for a hybrid application serving its pages with a rich JS content, which uses AJAX calls to webapi actions to perform its tasks. I need the app users to authenticate before they can work with the apps provided in these pages, so I think I'll be fine with the forms authentication. I thus need to add it to the existing WebApi project and let my authorized-only actions return a 302 (redirect user to login page) rather than a 401.
Anyway, I'm missing something because as soon as I try to use a WebSecurity method I get the following exception:
System.InvalidOperationException was caught
Message=To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
Source=WebMatrix.WebData
Could anyone suggest a fix? Here are the steps I took for adding authorization:
1) Web.config: add to system.web:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Add to appsettings (the 2nd entry is for replacing 401 with 302):
<add key="enableSimpleMembership" value="true"/>
<add key="webapi:EnableSuppressRedirect" value="false" />
Also remove profile, membership and rolemanager sections from the original template (they are not intended to be used with simple membership).
2) add NuGet packages for OpenAuth (DotNetOpenAuth Core, DotNetOpenAuth ext for ASP.NET, DotNetOpenAuth 1.0(a) consumer, DotNetOpenAuth 1.0(a), DotNetOpenAuth OpenID Core, DotNetOpenAuth OpenID Relying Party).
3) add InitializeSimpleMembership.cs to Filters (the code is pretty standard, see below).
4) copy from an MVC web app project the models in AccountModels.cs, all the views in Views/Account, and the AccountController.cs.
The InitializeSimpleMembership code is here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
private static void SeedData()
{
// seed data: users and roles
if (!WebSecurity.UserExists("TheAdminGuyName"))
WebSecurity.CreateUserAndAccount("TheAdminGuyName", "password");
if (!Roles.RoleExists("administrator")) Roles.CreateRole("administrator");
if (!Roles.IsUserInRole("TheAdminGuyName", "administrator"))
Roles.AddUserToRole("TheAdminGuyName", "administrator");
}
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "UserName", autoCreateTables: true);
SeedData();
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
This might be relevant, as it mentions your error specifically:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx