3
\$\begingroup\$

I need an external application that helps deployment of the web application. The application should create the aspnetdb database on the dedicated SQL server. Then it creates the wanted roles for the web application, and it defines the initial users, assigns their roles and properties. The code below works, but it probably needs some cleaning/refactoring (see the questions below the code):

The App.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <connectionStrings>
        <add name="aspnetdbConnectionString"
             connectionString="Data Source=COMPUTER\SQL2014INSTANCE;Initial Catalog=aspnetdb;Persist Security Info=True;User ID=sa;Password=saSQL2014INSTANCE"
             providerName="System.Data.SqlClient" />
    </connectionStrings>
    <system.web>
        <membership defaultProvider="SqlMembershipProv">
            <providers>
                <clear />
                <add name="SqlMembershipProv"
                     type="System.Web.Security.SqlMembershipProvider"
                     connectionStringName="aspnetdbConnectionString"
                     enablePasswordRetrieval="false"
                     enablePasswordReset="false"
                     requiresQuestionAndAnswer="false"
                     passwordFormat="Hashed"
                     />
            </providers>
        </membership>
        <profile defaultProvider="profileProv">
            <providers>
                <clear />
                <add name="profileProv"
                     type="System.Web.Profile.SqlProfileProvider" 
                     connectionStringName="aspnetdbConnectionString"
                     applicationName="web_app" />
            </providers>
            <properties>
               <add name="FullName" />
               <add name="Phone" />
            </properties>
        </profile>
    </system.web>
</configuration>

The Program.cs:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Web.Management;
using System.Web.Profile;
using System.Web.Security;

namespace InitAspnetdbAppRolesAndUsers
{
    class Program
    {
        static void Main(string[] args)
        {
            // The connection string of the following name used throughout the application.
            const string conStrName = "aspnetdbConnectionString";

            // Web application name.
            const string webAppName = "web_app";

            // Extract the info from the connection string.
            string conString =
                ConfigurationManager.ConnectionStrings[conStrName].ConnectionString;
            SqlConnectionStringBuilder cns = new SqlConnectionStringBuilder(conString);

            string SQLServerName = cns.DataSource; //@"COMPUTER\SQL2014INSTANCE";
            string SQLServerUser = cns.UserID;
            string SQLServerPassword = cns.Password;
            string ASPNETdbName = cns.InitialCatalog;

            // Create the aspnetdb database. It does not harm if it exists.
            SqlServices.Install(SQLServerName,
                                SQLServerUser, SQLServerPassword,
                                ASPNETdbName, SqlFeatures.All);

            // The RoleProvider could be removed from the App.config with using this.
            SqlRoleProvider roleProvider = new SqlRoleProvider();
            NameValueCollection roleProvConfig = new NameValueCollection {
                { "connectionStringName", conStrName},
                { "applicationName", webAppName }
            };
            roleProvider.Initialize("SqlRoleProv", roleProvConfig);

            // The roles to be created for the application.
            string[] roles = { "admin",    
                               "poweruser",
                               "export" };    
            foreach (var role in roles)
            {
                if (!roleProvider.RoleExists(role))
                    roleProvider.CreateRole(role);
            }

            // I was unsuccessfull to remove the <membership> part from the App.config.
            SqlMembershipProvider memProv = new SqlMembershipProvider();
            NameValueCollection memProvConfig = new NameValueCollection {
                { "connectionStringName", conStrName},
                { "applicationName", webAppName },
                { "enablePasswordRetrieval", "false" },
                { "enablePasswordReset", "false" },
                { "requiresQuestionAndAnswer", "false" },
                { "passwordFormat", "Hashed" },
            };
            memProv.Initialize("SqlMembershipProv", memProvConfig);

            // I was unsuccessfull to replace the <profile> in the App.config.
            // For example, I would like to use the code for the more general 
            // application, where the properties can differ for different web applications,
            // and it would not be hardwired in App.config.
            //
            // SqlProfileProvider profileProv = new SqlProfileProvider();
            // NameValueCollection profProvConfig = new NameValueCollection {
            //     { "connectionStringName", conStrName},
            //     { "applicationName", webAppName },
            // };
            // profileProv.Initialize("SqlProfileProv", memProvConfig);

            // It is possible to create the users; however, I would like to get cleaner code.
            List<string[]> userList = new List<string[]>
            {
                new[] { "admin", "adminPa$$word1234", "admin@example.com", "admin poweruser export", "Jan Admin", "111 222 333" },
                    new[] { "admin2", "admin2Pa$$word1234", "admin2@example.com", "admin poweruser", "Petr Admin", "222 333 444" },
                    new[] { "admin3", "admin3Pa$$word1234", "admin3@example.com", "admin", "Lenka Admin", "333 444 555" },
            };

            foreach (var info in userList)
            {
                string name = info[0];
                string password = info[1];
                string email = info[2];

                // Get the list of the roles. If no role is defined, the array
                // of the lenght 1 with empty string inside is created.
                // See the testing below.
                string[] rolenames = info[3].Split();

                string fullname = info[4];
                string phone = info[5];

                if (memProv.GetUser(name, false) == null)
                {
                    MembershipCreateStatus status = new MembershipCreateStatus();
                    memProv.CreateUser(name, password, email,
                        "none", // password question
                        "none", // password answer
                        true,   // isApproved
                        null,   //
                        out status);
                    Debug.Assert(status.Equals(MembershipCreateStatus.Success));

                    // Assign the roles to the user.
                    if (rolenames[0] != "")
                        roleProvider.AddUsersToRoles(new[] { name }, rolenames);

                    // Set the user properties.
                    ProfileBase pb = ProfileBase.Create(name);
                    pb.SetPropertyValue("FullName", fullname);
                    pb.SetPropertyValue("Phone", phone);
                    pb.Save();
                }
            }
        }
    }
}

I am rather new to C# (coming from C++, desktop native application to ASP.NET, C# web applications) -- I am not very strong in C#, and the ASP.NET infrastructure.

My initial idea was to have a kind of general application that reads some proprietary form of the config text file with the definitions to be interpreted. I tried to get rid of the App.config, but it seems like to pee against the wind.

So, the second idea was let the App.config exist, but not to use it for storing any information that can be specific for the deployment case. I tried to implement the most of the functionality in the Program.cs (here hardwired, but think in terms of getting the info say from another text file, imported from the Excel table, etc.). I was able to fully remove the role provider part from the App.config. However, I was not able to replace the membership provider, and the profile provider (including the properties definitions) from the App.config.

I was not able also to get rid of the connection string definition in the App.config. So, I have taken approach at least not to duplicate the information in the App.config and in the Program.cs (e.g. the connection string from the App.config parsed to get the information).

Anyway, notice the profile provider part of the App.config -- I do not know how to avoid duplication of the name of the web application. (I was able to do that for the other providers.)

I appreciate any help and idea how to improve the code and the approach.

\$\endgroup\$
6
\$\begingroup\$
static void Main(string[] args)

If you're not going to use args, you can omit them:

static void Main()

const string conStrName = "aspnetdbConnectionString";
SqlConnectionStringBuilder cns = new SqlConnectionStringBuilder(conString);

Try not to abbreviate variable names, it makes your code harder to understand.

Also, consider using var, especially if the type of the variable is clear from the right side of the assignment.


string SQLServerName = cns.DataSource;

Local variables should start with a lowercase letter: sqlServerName.


List<string[]> userList = new List<string[]>
{
    new[] { "admin", "adminPa$$word1234", "admin@example.com", "admin poweruser export", "Jan Admin", "111 222 333" },
    new[] { "admin2", "admin2Pa$$word1234", "admin2@example.com", "admin poweruser", "Petr Admin", "222 333 444" },
    new[] { "admin3", "admin3Pa$$word1234", "admin3@example.com", "admin", "Lenka Admin", "333 444 555" },
};

foreach (var info in userList)
{
    string name = info[0];
    string password = info[1];
    string email = info[2];

You shouldn't use arrays to store this kind of information, it's logically not an array.

You can use an anonymous type instead:

new { name = "admin", password = "adminPa$$word1234", email = "admin@example.com", roleNames = new[] { "admin", "poweruser", "export" }, fullName = "Jan Admin", phone = "111 222 333" }

Note that this means it's much harder to confuse which array index contains which value and you can (and should) use types other than string.

(The incorrect indentation in the original code is due to a bug.)


MembershipCreateStatus status = new MembershipCreateStatus();
memProv.CreateUser(name, password, email,
    "none", // password question
    "none", // password answer
    true,   // isApproved
    null,   //
    out status);
  • You don't need to initialize out variables. (And newing up an enum doesn't make much sense either.)

  • Instead of comments, you should use named arguments, that way, the compiler verifies the names for you (comments can be wrong, code can't).

Put together:

MembershipCreateStatus status;
memProv.CreateUser(name, password, email,
    passwordQuestion: "none",
    passwordAnswer: "none",
    isApproved: true,
    providerUserKey: null,
    status: out status);

status.Equals(MembershipCreateStatus.Success)

You can compare enums using ==:

status == MembershipCreateStatus.Success
\$\endgroup\$
  • \$\begingroup\$ Thanks, Petr (my qualified guess for @svick ;) for the valuable comments regarding C#. I will definitely use the suggestions. I am more concerned with the problem of the used ASP.NET classes and of the placement of data/code to App.config vs. Program.cs. Is this a good place to ask these questions? \$\endgroup\$ – pepr Oct 18 '15 at 21:07
  • \$\begingroup\$ @pepr I think Code Review is more about reviewing code, if you want to help with design, Programmers SE might be more appropriate. (And yes, my name is Petr.) \$\endgroup\$ – svick Oct 18 '15 at 21:20
2
\$\begingroup\$

Before you try to opmizie your code you should first split it into smaller parts that are responsible for only a single thing. Currently the whole logic is in a huge single method that makes it hard to think about modules that perform some task.

Here're a few examples of such components/methods/modules:

  • ReadConfiguration
  • Install/Create Database
  • PopulateDatabase
  • ConfigureUsers

I'm not sure what your code exaclty does but try to define steps for the deployment and refactor them as let's say methods. If you then find that some of them perform similar things it might be good to put them into a new class. This way you will be able to better focus on a particular task rather then on everything at once.

\$\endgroup\$
  • \$\begingroup\$ Thanks. I am aware of that. The question contains the bare-bone example, the principle. In my opinion, this is a tiny program. \$\endgroup\$ – pepr Oct 16 '15 at 19:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.