how to implement a Multi-Tenant-Asp.net MVC application? - c#

I have an Asp.net MVC3 application I want to be able to allow multiple/ different clients to access the same application but using different url's. I have already managed to configure the database to allow this. So hia's the main part i want to host my application in a domain say... www.myapplication.com then allow different client to access the same application using 1.www.clientOne.myapplication.com 2.www.clientTwo.myapplication.com.
How to do it? Please provide the code. Thanks.

Stack Exchange itself runs on a multi-tenant architecture!
http://weblogs.asp.net/zowens/archive/2010/06/16/multi-tenant-asp-net-mvc-views.aspx
This set of articles should get you what you need to set up your basic architecture. It has plenty of code and does a pretty good job at covering what you'd need. I don't think you're going to get the exact code that you need here to set up the entire core architecture of your multi tenan application, I'd advise using either this article above or ones like it.

To implement multi tenant application you have to do some tricks in
C:\Windows\System32\drivers\etc host file
and some coding tricks in App_Start folder at RouteConfig.Csfile by inheriting and implementing IRouteConstraint interface. Also need to see bindings settings in IIS server(for this you need to see the video tutorial provided in below link)
You can get a complete code here https://github.com/ashikcse20/ASP-MVC5-MULTI-TENANT-REPOSITORY with complete written tutorial.
The video tutorial is given here ASP .NET MVC 5 Multi Tenant Example With Basic Code (Single Database Per Tenant)
Code in RouteConfig.Cs at RegisterRoutes function
routes.MapRoute(
name: "Default", url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { TenantRouting = new RoutingConstraint() }
);
Inheriting and implementing IRouteConstraint Interface
public class RoutingConstraint : IRouteConstraint // It is main Class for Multi teanant
{
public bool Match(HttpContextBase httpContext, Route route, string getParameter, RouteValueDictionary values, RouteDirection routeDirection)
{
// Got htis code from http://blog.gaxion.com/2017/05/how-to-implement-multi-tenancy-with.html
var GetAddress = httpContext.Request.Headers["Host"].Split('.');
var tenant = GetAddress[0];
//Here you can apply your tricks and logic. Note for when you put it in public server then www.hamdunsoft.com , www.tenant1.hamdunsoft.com then you need to change a little bit in the conditions . Because a www. was added.
if (GetAddress.Length < 2) // See here for localhost:80 or localhost:9780 ohh also for hamdun soft execution will enter here . But for less than 2? will hamdunsoft.com enter here?
{
tenant = "This is the main domain";
Constant.DatabaseName = "TEST";
if (!values.ContainsKey("tenant"))
values.Add("tenant", tenant);
//return false;
// return true;
}
else if (GetAddress.Length == 2) // execution will enter here for hamdunsoft.com enter here but not for www.hamdunsoft.com
{
tenant = "This is the main domain";
Constant.DatabaseName = GetAddress[0];
if (!values.ContainsKey("tenant"))
values.Add("tenant", tenant);
//return false;
// return true;
}
else if (!values.ContainsKey("tenant")) // for tenant1.hamdunsoft.com execution will enter here
{
values.Add("tenant", tenant);
Constant.DatabaseName = GetAddress[1]+"."+ tenant;
}
return true;
}
}

Related

How to filter UrlReferrer from a whitelist in ASP.NET?

I need to create a single page that is going to be a part of an existing ASP.NET MVC website that is only open or accessible if came from allowed websites (Referrer). Our partners are planning
to load this page using an iFrame. This page allows any user to quickly enter the details and save into
our database.
example: www.mysecuredwebsite.com/publicpage
Since this is going to be public single page only and no logins, the only security is
only allow if the referrer is in the list of allowed sites.
Example:
www.abc.com
www.randomwebsite.com
www.amazingsite.com
Any request from any other sites will be redirected to an error page.
What is the best approach to this problem? I'm thinking of creating a custom attribute that will be used
to decorate the controller which then reads
a list of allowed sites from the app.config orany type of config.
Or maybe from the SQLDB since the site is using SQLDB? The list I think will be frequently change
depending on the growing number of clients. It should be easily configurable and does not require
redeployment.
Any thoughts? Thanks!
You can use two methods
Method 1: Use ActionFilterAttribute.
Method 2: Use the Application_BeginRequest method in the Global.asax file.
Note: It is better to put the allowed urls in the database so that it can be easily changed.
I will explain the first method
The following filter checks whether UrlReferrer is allowed. If it
is not allowed, it will be referred to the error page.
add UrlReferrerFilterAttribute
using System.Web.Mvc;
using System.Web.Routing;
namespace yournamespace
{
public class UrlReferrerFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string urlReferrer = string.Empty;
if (filterContext.HttpContext.Request.UrlReferrer != null)
{
urlReferrer = filterContext.HttpContext.Request.UrlReferrer.AbsoluteUri;
if(!db.UrlReferrerTable.Any(a => a.Url == urlReferrer))
{
var values = new RouteValueDictionary(new
{
action = "ErrorPage",
controller = "Home"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
base.OnActionExecuting(filterContext);
}
}
}
now use
[UrlReferrerFilter]
public ActionResult YourAction()
{
//..................
//..................
}

Grapevine Rest Server Route Pathinfo with dynamic values

I'm pretty new to C# and need to realise a REST Service so i stumbled over Grapevine.
I need to have parts of the URL of the service handed over on service start via config file but I don't manage to hand over the value "clientId" of the config file to the Route's Pathinfo because it's not constant.
Here's the part of the code:
[RestResource(BasePath = "/RestService/")]
public class Rest_Resource
{
public string clientId = ConfigurationManager.AppSettings["ClientId"];
[RestRoute(PathInfo = clientId + "/info")]//<-how do I fill Pathinfo with dynamic values?
public IHttpContext GetVersion(IHttpContext context)
{....}
}
I'm using grapevine v4.1.1 as nuget package in visual studio.
While it is possible to change attribute values at runtime, or even use dynamic attributes, an easier solution in this case might be to not use the auto discovery feature exclusively, but use a hybrid approach to route registration.
Consider the following class that contains two rest routes, but only one of them is decorated with the attribute:
[RestResource(BasePath = "/RestService/")]
public class MyRestResources
{
public IHttpContext ManuallyRegisterMe(IHttpContext context)
{
return context;
}
[RestRoute(PathInfo = "/autodiscover")]
public IHttpContext AutoDiscoverMe(IHttpContext context)
{
return context;
}
}
Since you want to register the first route using a value that is not known until runtime, we can manually register that route:
// Get the runtime value
var clientId = "someValue";
// Get the method info
var mi = typeof(MyRestResources).GetMethod("ManuallyRegisterMe");
// Create the route
var route = new Route(mi, $"/RestService/{clientId}");
// Register the route
server.Router.Register(route);
This takes care of manually registering our route that needs a runtime value, but we still want the other routes to be automatically discovered. Since the router will only autodiscover if the routing table is empty when the server starts, we'll have to tell the router when to scan the assemblies. You can do this either before or after you manually register the route:
server.Router.ScanAssemblies();

How do I dynamically choose a controller in MVC Core

I have a situation where a site may need a link to redirect to certain controllers based on database results.
For example:
site.com/abcd
needs to return the result from a Item Controller, which would normally be called as /item/view/123
The key here is that I can't hard code the abcd into the routing. And some links may go to an Item Controller, others may go to an Orders controller.
I've tried a catchall route to a controller than then loads up the desired controller, but the environment is not set so it does not work properly (it can't find the views).
You can get whatever behavior you desire by implementing IRouter as in this answer, including basing your logic on data from an external source (such as a config file or database).
This is much more flexible than a catchall route because it lets you choose the controller on the fly.
public class MyRoute : IRouter
{
private readonly IRouter innerRouter;
public MyRoute(IRouter innerRouter)
{
if (innerRouter == null)
throw new ArgumentNullException("innerRouter");
this.innerRouter = innerRouter;
}
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
if (!requestPath.StartsWith("abcd"))
{
return;
}
//Invoke MVC controller/action
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Routers.Add(this.innerRouter);
newRouteData.Values["controller"] = "Item";
newRouteData.Values["action"] = "View";
newRouteData.Values["id"] = 123;
try
{
context.RouteData = newRouteData;
await this.innerRouter.RouteAsync(context);
}
finally
{
// Restore the original values to prevent polluting the route data.
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
VirtualPathData result = null;
var values = context.Values;
var controller = Convert.ToString(values["controller"]);
var action = Convert.ToString(values["action"]);
var id = Convert.ToString(values["id"]);
if ("Item".Equals(controller) && "View".Equals(action))
{
result = new VirtualPathData(this, "abcd?id=" + id);
context.IsBound = true;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
}
Usage
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.Routes.Add(new MyRoute(
innerRouter: routes.DefaultHandler)
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
The GetVirtualPath should mirror what the RouteAsync does. RouteAsync converts a URL into route values, and the GetVirtualPath should convert the same route data back into the same URL.
The easiest way to accomplish this is to use a data structure to create a two-way mapping between these 2 data points (as in the linked answer) so you don't have to continually change the logic within these 2 methods. This data structure should be cached and not do anything too resource intensive, since every request will use it to determine where to send each URL.
Alternatively, you could create a separate route for each of your individual pieces of logic and register them all at application startup. However, you need to ensure they are registered in the correct order and that each route will only match the correct set of URLs and correct set of RouteValues.
NOTE: For a scenario such as this you should almost never need to use RedirectToAction. Keep in mind redirecting will send an HTTP 302 request to the browser, which tells it to lookup another location on your server. This is unnecessary overhead in most cases because it is much more efficient just to route the initial request to the controller you want.

How to route Optional URI parameters to API Controller functions

I am trying to set up an Asp.Net forms site with an API.
I have succeeded in adding in selective authentication, so that pages starting "\api\" do not get redirected, but instead challenge for basic authentication.
I am now trying to use MS Web Api 2 to do the API routing.
The idea is to be as RESTful as possible. I have a resource, a TradableItem, and initially I would want to allow API users to use HTTP GET in one of two ways.
If the API user passes no item key, the user receives a list of possible item keys
["ABC","DEF"...]
If the API user passes an item key as part of the URI, eg "/api/tradables/abc", a representation of the TradableItem is returned for the one with the key=ABC. (To my understanding, this is standard REST behaviour).
In Global.ASAX's Application_Start() function I have a route map like so...
RouteTable.Routes.MapHttpRoute(
name: "TradableItemVerbs",
routeTemplate: "api/tradables/{item}",
defaults: new { item = System.Web.Http.RouteParameter.Optional, controller = "Tradable" });
The TradableController.cs file looks like this...
public class TradableController : ApiController
{
private static CustomLog logger = new CustomLog("TradableController");
// GET api/<controller>
public IEnumerable<string> GetKeys()
{
var prefix = "GetKeys() - ";
string msg = "";
msg = "Function called, returning list of tradable pkeys...";
logger.Debug(prefix + msg);
// Get a list of tradable items
return TradableManager.GetTradablePkeys();
}
// GET api/<controller>/<pkey>
public string GetTradable(string pkey)
{
string msg = string.Format("Would get Tradable data for key: >{0}<", pkey);
return msg;
}
}
The problem is that only the GetKeys() function fires, whether I call GET to "/api/tradables" or "/api/tradables/abc".
For reference, using VS2015 Community, IIS 7.5, targeting .Net 4.6.1. I used Rick Strahl's blog as a guide on this (among other sources).
http://weblog.west-wind.com/posts/2012/Aug/21/An-Introduction-to-ASPNET-Web-API#HTTPVerbRouting
please, change the name of your param to item (because, this is the name define in the routes):
public string GetTradable(string item)
{
....
}
or when you call the method be explicit with the parameter name: /api/tradables?pkey=abc

Redirect users with suspended accounts without creating redirect loop

I have a subscription based MVC 2 application with the basic .NET Membership service in place (underneath some custom components to manage the account/subscription, etc). Users whose accounts have lapsed, or who have manually suspended their accounts, need to be able to get to a single view in the system that manages the status of their account. The controller driving that view is protected using the [Authorize] attribute.
I want to ensure that no other views in the system can be accessed until the user has re-activated their account. In my base controller (from which all my protected controllers derive) I tried modifying the OnActionExecuting method to intercept the action, check for a suspended account, and if it's suspended, redirect to the single view that manages the account status. But this puts me in an infinite loop. When the new action is hit, OnActionExecuting gets called again, and the cycle keeps going.
I don't really want to extend the [Authorize] attribute, but can if need be.
Any other thoughts on how to do this at the controller level?
EDIT: in the base controller, I was managing the redirect (that subsequently created the redirect loop) by modifying the filterContext.Result property, setting it to the RedirectToAction result of my view in question. I noticed everytime the loop occurs, filterContext.Result == null. Perhaps I should be checking against a different part of filterContext?
Ok, so here's my solution in case it helps anyone else. There's got to be a more elegant way to do this, and I'm all ears if anyone has a better idea.
In my BaseController.cs:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["CurrentUser"] = CurrentUser; // this is a public property in the BaseController
if (CurrentUser != null && CurrentUser.Account.Status != AccountStatus.Active)
{
// if the account is disabled and they are authenticated, we need to allow them
// to get to the account settings screen where they can re-activate, as well as the logoff
// action. Everything else should be disabled.
string[] actionWhiteList = new string[] {
Url.Action("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" }),
Url.Action("Logoff", "Account")
};
var allowAccess = false;
foreach (string url in actionWhiteList)
{
// compare each of the whitelisted paths to the raw url from the Request context.
if (url == filterContext.HttpContext.Request.RawUrl)
{
allowAccess = true;
break;
}
}
if (!allowAccess)
{
filterContext.Result = RedirectToAction("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" });
}
}
base.OnActionExecuting(filterContext);
}

Categories