This post describes how you can set up one website that handles multiple domain names in ASP.NET Core. A multi-domain website is cheaper to host, easier to maintain and easier to administer compared to multiple websites. A single multidomain website enables you to customize content, language and design for each domain.
A multidomain website enables you to translate your website to different languages and/or to have multiple shops on a website by connecting products to domains. You can connect your models to a domain, to a language and/or to a shop. You can combine connections, some models can be connected to a language and some models can be conntected to a domain.
A multiple domain website stores domain models in a database, gets the current domain on a request and use the properties in the domain model to fetch other resources like static texts and a page or a product.
Model
A domain model includes the properties that is needed to correctly connect resources to the domain name. The domain_name property is used to find the current domain.
public class WebDomain
{
#region Variables
public Int32 id { get; set; }
public string website_name { get; set; }
public string domain_name { get; set; }
public string web_address { get; set; }
public Int32 front_end_language { get; set; }
public Int32 back_end_language { get; set; }
public string analytics_tracking_id { get; set; }
public string facebook_app_id { get; set; }
public string facebook_app_secret { get; set; }
public string google_app_id { get; set; }
public string google_app_secret { get; set; }
public bool noindex { get; set; }
#endregion
#region Constructors
public WebDomain()
{
// Set values for instance variables
this.id = 0;
this.website_name = "";
this.domain_name = "";
this.web_address = "";
this.front_end_language = 0;
this.back_end_language = 0;
this.analytics_tracking_id = "";
this.facebook_app_id = "";
this.facebook_app_secret = "";
this.google_app_id = "";
this.google_app_secret = "";
this.noindex = false;
} // End of the constructor
public WebDomain(SqlDataReader reader)
{
// Set values for instance variables
this.id = Convert.ToInt32(reader["id"]);
this.website_name = reader["website_name"].ToString();
this.domain_name = reader["domain_name"].ToString();
this.web_address = reader["web_address"].ToString();
this.front_end_language = Convert.ToInt32(reader["front_end_language"]);
this.back_end_language = Convert.ToInt32(reader["back_end_language"]);
this.analytics_tracking_id = reader["analytics_tracking_id"].ToString();
this.facebook_app_id = reader["facebook_app_id"].ToString();
this.facebook_app_secret = reader["facebook_app_secret"].ToString();
this.google_app_id = reader["google_app_id"].ToString();
this.google_app_secret = reader["google_app_secret"].ToString();
this.noindex = Convert.ToBoolean(reader["noindex"]);
} // End of the constructor
#endregion
} // End of the class
Repository class
Our repository class for web domains includes all the methods that we need to manage web domains on our website. We are using MS SQL as database and distributed cache to speed up the time to get the current domain.
public class WebDomainRepository : IWebDomainRepository
{
#region Variables
private readonly IMsSqlDatabaseRepository database_repository;
private readonly IDistributedCache distributed_cache;
private readonly CacheOptions cache_options;
#endregion
#region Constructors
public WebDomainRepository(IMsSqlDatabaseRepository database_repository, IDistributedCache distributed_cache, IOptions<CacheOptions> cache_options)
{
this.database_repository = database_repository;
this.distributed_cache = distributed_cache;
this.cache_options = cache_options.Value;
} // End of the constructor
#endregion
#region Insert methods
public Int32 Add(WebDomain post)
{
// Create the int to return
Int32 idOfInsert = 0;
// Create the sql statement
string sql = "INSERT INTO dbo.web_domains (website_name, domain_name, web_address, front_end_language, back_end_language, "
+ "analytics_tracking_id, facebook_app_id, facebook_app_secret, google_app_id, google_app_secret, noindex) "
+ "VALUES (@website_name, @domain_name, @web_address, @front_end_language, @back_end_language, "
+ "@analytics_tracking_id, @facebook_app_id, @facebook_app_secret, @google_app_id, @google_app_secret, @noindex);SELECT CAST(SCOPE_IDENTITY() AS INT);";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@website_name", post.website_name);
parameters.Add("@domain_name", post.domain_name);
parameters.Add("@web_address", post.web_address);
parameters.Add("@front_end_language", post.front_end_language);
parameters.Add("@back_end_language", post.back_end_language);
parameters.Add("@analytics_tracking_id", post.analytics_tracking_id);
parameters.Add("@facebook_app_id", post.facebook_app_id);
parameters.Add("@facebook_app_secret", post.facebook_app_secret);
parameters.Add("@google_app_id", post.google_app_id);
parameters.Add("@google_app_secret", post.google_app_secret);
parameters.Add("@noindex", post.noindex);
// Insert the post
this.database_repository.Insert<Int32>(sql, parameters, out idOfInsert);
// Return the id of the inserted item
return idOfInsert;
} // End of the Add method
#endregion
#region Update methods
public void Update(WebDomain post)
{
// Create the sql statement
string sql = "UPDATE dbo.web_domains SET website_name = @website_name, domain_name = @domain_name, web_address = @web_address, "
+ "front_end_language = @front_end_language, back_end_language = @back_end_language, "
+ "analytics_tracking_id = @analytics_tracking_id, facebook_app_id = @facebook_app_id, facebook_app_secret = @facebook_app_secret, "
+ "google_app_id = @google_app_id, google_app_secret = @google_app_secret, noindex = @noindex WHERE id = @id;";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@website_name", post.website_name);
parameters.Add("@domain_name", post.domain_name);
parameters.Add("@web_address", post.web_address);
parameters.Add("@front_end_language", post.front_end_language);
parameters.Add("@back_end_language", post.back_end_language);
parameters.Add("@analytics_tracking_id", post.analytics_tracking_id);
parameters.Add("@facebook_app_id", post.facebook_app_id);
parameters.Add("@facebook_app_secret", post.facebook_app_secret);
parameters.Add("@google_app_id", post.google_app_id);
parameters.Add("@google_app_secret", post.google_app_secret);
parameters.Add("@noindex", post.noindex);
// Update the post
this.database_repository.Update(sql, parameters);
} // End of the Update method
#endregion
#region Count methods
public Int32 GetCountBySearch(string[] keywords)
{
// Create the sql statement
string sql = "SELECT COUNT(id) AS count FROM dbo.web_domains WHERE 1 = 1";
for (int i = 0; i < keywords.Length; i++)
{
sql += " AND (website_name LIKE @keyword_" + i.ToString() + " OR domain_name LIKE @keyword_" + i.ToString() + ")";
}
sql += ";";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
for (int i = 0; i < keywords.Length; i++)
{
parameters.Add("@keyword_" + i.ToString(), "%" + keywords[i].ToString() + "%");
}
// Get the count
Int32 count = this.database_repository.GetCount<Int32>(sql, parameters);
// Return the count
return count;
} // End of the GetCountBySearch method
#endregion
#region Get methods
public WebDomain GetCurrentDomain(HttpContext context)
{
// Get the domain name
string domain_name = context.Request.Host.ToString();
// Replace www.
domain_name = domain_name.Replace("www.", "");
// Get the web domain post
WebDomain domain = GetFromCache(domain_name);
// Make sure that the domain not is null
if (domain == null)
{
domain = new WebDomain();
domain.id = 0;
domain.domain_name = "localhost";
domain.web_address = "https://localhost:80";
domain.front_end_language = 2;
domain.back_end_language = 2;
domain.analytics_tracking_id = "";
domain.facebook_app_id = "";
domain.facebook_app_secret = "";
domain.google_app_id = "";
domain.google_app_secret = "";
domain.noindex = true;
}
// Return the domain
return domain;
} // End of the GetCurrentDomain method
public KeyStringList GetDomainImageUrls(IHostingEnvironment environment, Int32 domain_id, bool showNoImageIcon)
{
// Create the list to return
KeyStringList imageUrls = new KeyStringList(5);
// Create paths
string directoryPath = "/domains/" + domain_id.ToString() + "/images/";
// Add images to the key string list
imageUrls.Add("background_image", directoryPath + "background_image.jpg");
imageUrls.Add("default_logotype", directoryPath + "default_logotype.png");
imageUrls.Add("mobile_logotype", directoryPath + "mobile_logotype.png");
imageUrls.Add("big_icon", directoryPath + "big_icon.png");
imageUrls.Add("small_icon", directoryPath + "small_icon.png");
if (showNoImageIcon == true)
{
// Create the no image path
string noImagePath = "/images/no_image_wide.jpg";
// Get all the keys in the dictionary
List<string> keys = imageUrls.dictionary.Keys.ToList<string>();
// Loop all the keys
for (int i = 0; i < keys.Count; i++)
{
// Get the url
string url = environment.WebRootPath + imageUrls.Get(keys[i]);
// Check if the file exists
if (System.IO.File.Exists(url) == false)
{
imageUrls.Update(keys[i], noImagePath);
}
}
}
// Return the list
return imageUrls;
} // End of the GetDomainImageUrls method
public WebDomain GetOneById(Int32 id)
{
// Create the sql statement
string sql = "SELECT * FROM dbo.web_domains WHERE id = @id;";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@id", id);
// Get a post
WebDomain post = this.database_repository.GetModel<WebDomain>(sql, parameters);
// Return the post
return post;
} // End of the GetOneById method
public WebDomain GetFromCache(string domain_name)
{
// Create the post to return
WebDomain post = null;
// Get the cached settings
string data = this.distributed_cache.GetString(domain_name);
if (data == null)
{
// Get the post
post = GetOneByDomainName(domain_name);
// Make sure that something was found in the database
if (post != null)
{
// Create cache options
DistributedCacheEntryOptions cacheEntryOptions = new DistributedCacheEntryOptions();
cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(this.cache_options.ExpirationInMinutes));
cacheEntryOptions.SetAbsoluteExpiration(TimeSpan.FromMinutes(this.cache_options.ExpirationInMinutes));
this.distributed_cache.SetString(domain_name, JsonConvert.SerializeObject(post), cacheEntryOptions);
}
}
else
{
post = JsonConvert.DeserializeObject<WebDomain>(data);
}
// Return the post
return post;
} // End of the GetFromCache method
public WebDomain GetOneByDomainName(string domain_name)
{
// Create the sql statement
string sql = "SELECT * FROM dbo.web_domains WHERE domain_name = @domain_name;";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@domain_name", domain_name);
// Get a post
WebDomain post = this.database_repository.GetModel<WebDomain>(sql, parameters);
// Return the post
return post;
} // End of the GetOneByDomainName method
public IList<WebDomain> GetAll(string sort_field, string sort_order)
{
// Make sure that sort variables are valid
sort_field = GetValidSortField(sort_field);
sort_order = GetValidSortOrder(sort_order);
// Create the sql statement
string sql = "SELECT * FROM dbo.web_domains ORDER BY " + sort_field + " " + sort_order + ";";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
// Get the list of posts
IList<WebDomain> posts = this.database_repository.GetModelList<WebDomain>(sql, parameters, 10);
// Return the list of posts
return posts;
} // End of the GetAll method
public IList<WebDomain> GetBySearch(string[] keywords, Int32 page_size, Int32 page_number, string sort_field, string sort_order)
{
// Make sure that sort variables are valid
sort_field = GetValidSortField(sort_field);
sort_order = GetValidSortOrder(sort_order);
// Create the sql statement
string sql = "SELECT * FROM dbo.web_domains WHERE 1 = 1";
for (int i = 0; i < keywords.Length; i++)
{
sql += " AND (website_name LIKE @keyword_" + i.ToString() + " OR domain_name LIKE @keyword_" + i.ToString() + ")";
}
sql += " ORDER BY " + sort_field + " " + sort_order + " OFFSET @pageNumber ROWS FETCH NEXT @pageSize ROWS ONLY;";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@pageNumber", (page_number - 1) * page_size);
parameters.Add("@pageSize", page_size);
for (int i = 0; i < keywords.Length; i++)
{
parameters.Add("@keyword_" + i.ToString(), "%" + keywords[i].ToString() + "%");
}
// Get the list of posts
IList<WebDomain> posts = this.database_repository.GetModelList<WebDomain>(sql, parameters, page_size);
// Return the list of posts
return posts;
} // End of the GetBySearch method
#endregion
#region Delete methods
public Int32 DeleteOnId(Int32 id)
{
// Create the sql statement
string sql = "DELETE FROM dbo.web_domains WHERE id = @id;";
// Create parameters
IDictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@id", id);
// Delete the post
Int32 errorNumber = this.database_repository.Delete(sql, parameters);
// Return error code
return errorNumber;
} // End of the DeleteOnId method
#endregion
#region Helper methods
public void RemoveFromCache()
{
// Get all domains
IList<WebDomain> posts = GetAll("domain_name", "ASC");
// Loop domains
for (int i = 0; i < posts.Count; i++)
{
// Get the data
string data = this.distributed_cache.GetString(posts[i].domain_name);
// Only remove the cache if it exists
if (data != null)
{
// Remove data from cache
this.distributed_cache.Remove(posts[i].domain_name);
}
}
} // End of the RemoveFromCache method
#endregion
#region Validation
public string GetValidSortField(string sort_field)
{
// Make sure that the sort field is valid
if (sort_field != "id" && sort_field != "website_name" && sort_field != "domain_name")
{
sort_field = "id";
}
// Return the string
return sort_field;
} // End of the GetValidSortField method
public string GetValidSortOrder(string sort_order)
{
// Make sure that the sort order is valid
if (sort_order != "ASC" && sort_order != "DESC")
{
sort_order = "ASC";
}
// Return the string
return sort_order;
} // End of the GetValidSortOrder method
#endregion
} // End of the class
Requests
We get the current domain each time there is a request on our website. The current domain is used to fetch other resources, like static texts and images.
[HttpGet]
[Authorize(Roles = "Administrator,Editor,Translator")]
public IActionResult Index()
{
// Get the current domain
WebDomain current_domain = this.web_domain_repository.GetCurrentDomain(ControllerContext.HttpContext);
// Get translated texts
KeyStringList tt = this.static_text_repository.GetFromCache(current_domain.back_end_language, "id", "ASC");
// Set form data
ViewBag.CurrentDomain = current_domain;
ViewBag.TranslatedTexts = tt;
ViewBag.QueryParams = new QueryParams(ControllerContext.HttpContext.Request);
// Return the view
return View();
} // End of the index method