Multi Tenant Razor pages - asp.net-mvc

I am trying to set up Razor Pages routing to allow different views to be rendered for different tenants.
I have a directory structure as follows:
/Pages
Test.cshtml.cs
/Tenant1
Test.cshtml
/Tenant2
Test.cshtml
Given I am already able to decide which tenant is required, how is it possible to configure the routing to map some path eg localhost:8080/Test to either Tenant1/Test or Tenant2/Test views.

Use dynamic view content (via partial views).
With this solution, the Test page will dynamically load a different view depending on the route used to call it.
This means that you only have a single Test page but inside the cshtml file you will grab content from a partial view (more on that in a second).
First you will need to rename the files like so....
/Pages
Test.cshtml.cs
/Tenant1
_Test.cshtml // note it is prefixed with an underscore!
/Tenant2
_Test.cshtml // prefixed with an underscore too.
The naming convention for a partial view is to prefix the file with an underscore (_). This will immediately identify to someone looking at your project files as a "non-routable" page.
Then you add a little bit of logic to render the partial views...
Test.cshtml
#{
switch(...) // used a switch statement to illustrate the solution
{
case "Tenant1":
await Html.PartialAsync("~/Pages/Tenant1/_Test.cshtml");
break;
case "Tenant2":
await Html.PartialAsync("~/Pages/Tenant2/_Test.cshtml");
break;
default:
throw new NotImplementedException();
}
}
You can read about partial views here.
Extra: Using the same page model.
I also noticed that you had wanted to use the same page model (meaning sharing Test.cshtml.cs for both. This is rather trivial, but for the sake of completeness of the answer here is how you would do that...
/Pages/Test.cshtml.cs
namespace Foo.Pages
{
public class MySharedTestModel : PageModel
{
...
}
}
/Pages/Tenant1/Test.cshtml and /Pages/Tenant2/Test.cshtml
#page
#using Foo.Pages
#model MySharedTestModel
...

Related

dynamically create partial view (CSHTML)

Is it possible in ASP.NET MVC 5 to dynamically create a partial view (cshtml) in the /Views/Shared directory? I have a situation where people are going to upload a bunch of HTML as strings and was thinking it would be better for performance to store them on the file system.
Is it as simple as creating a new file, steaming a string and saving?
Yes it is possible
Simply make a view like DynamicView.cshtml
#model DynamicView
#Html.Raw(Model.HTMLString)
Now the method you will use to store both the HTML and the pointer to it is a different story. You can either store the sanitized HTML in a data-base and retrieve it with a call to the Controller like
public ActionResult DynamicView(ind id)
{
DynamicView model = new DynamicView();
DynamicView.HTMLString = dbContext.HTMLViews.Where(v => v.id == id);
return View(model);
}
If you wish to write the submitted HTML to files instead, you can instead do
public ActionResult DynamicView(string filePath)
{
DynamicView model = new DynamicView();
DynamicView.HTMLString = ...code that reads file
return View(model);
}
See this related post Writing/outputting HTML strings unescaped
Quick Answer: No (you cannot/should not modify Views Folder at RunTime.)
ASP.NET MVC Views and Shared Views are meant to be compiled and run. Modifying them or Adding new ones as the Application is running is not at all advisable or practical.
What would make more sense is for you to store the uploaded html blobs in a database, file system, storage blob (cloud). Then code your Shared View or Specific Views to retrieve specific html blobs from the stored location depending upon which user is logged in.
There are a whole lot of Extension Functions in MVC that Enable you to insert partial html in views. take a look at PartialExtensions as an starting point.
I had a requirement to create top menu which will generate from DB. I tried a lot to create it using partial view. But i found most of the cases partial view is use for static content. at last i fond some helpful tutorial please find the following i hope it will help you too.
click here to see how to create Dynamic Partial view

Finding Razor partial views in the area EditorTemplates folder

I am starting to use EditorFor helper method to render my razor partial views, but I couldn't get the partials in the Areas folder to work.
Here is the path to the partial:
~\Areas\Products\Views\Shared\EditorTemplates\_Edit.cshtml
The partial is really simple with only one "div" tag to do the testing.
Try to use in my page view (~\Areas\Products\Views\EditPage.cshtml) as
#Html.EditorFor(m => m.ProductEditModel, "_Edit")
Visual studio tells me that "Cannot resolve template '_Edit'".
Now, if I move the partial to the root view folder:
~\Views\Shared\EditorTemplates\_Edit.cshtml
It works, Visual studio has no problems to resolve the template, and the div is renderred correctly in my browser.
I also tried to customize the RazorViewEngine, did not work either
namespace MySite.Web
{
public class RazorViewEngine : System.Web.Mvc.RazorViewEngine
{
public RazorViewEngine()
: this(null)
{
}
public RazorViewEngine(IViewPageActivator viewPageActivator)
: base(viewPageActivator)
{
AreaPartialViewLocationFormats = new[]
{
"~/Areas/{2}/Views/Shared/EditorTemplates/{0}.cshtml"
}.Union(AreaPartialViewLocationFormats).ToArray();
}
}
}
Just wondering what did I do wrong? BTW, I am using MVC3 at the moment, can't upgrate to MVC4 due to some old components.
When calling a partial view or view from a different area in MVC, specify the full path of the partial view or view. Since MVC is based on convention, by convention it will look in the same area the calling code in the view (or controller) resides for any partial views or views referenced, unless a specific path is used. Try using the full path to reference the partial view when it is located in the products area:
#Html.EditorFor(m => m.ProductEditModel, "~/Areas/Products/Views/Shared/EditorTemplates/_Edit.cshtml")
Since the view referenced is a shared view it doesn't matter if you specify the full path if you are in the same area. However, if you are trying to access a view within a different directory than the view trying to reference it and this directory is not named shared you will need to specify the full path regardless of area. It is similar when the controller calls the view; if a controller from the same area as the referenced view specifies the short name for the view and this view is from a parent directory named different than its own (ignoring the "controller" suffix) the view engine will not find your view. Unless of course the parent directory for the view is in the shared folder.
Whether it's in the controller or a view you can't use the "short name" across areas because the view engine has a convention for where to look when the path isn't used. Areas are meant to do this to keep your code separated, or decoupled if you will, at a high level by default. So any decision to "cross the barrier" should be thought about mindfully, but certainly not discouraged. It's all about convention.
I am answering my own question now.. My page view path was not correct. Since my area is Products, controller is ProductController, my page view should be placed in ~\Areas\Products\Views\Product\EditPage.cshtml, that way, it matches what the view engine expects, and the partial will be corrected resolved.

route to reflect hierarchical url/menu structure

i've created a basic mvc3 website whereby each controller represents the first folder in a url structure.
for example, the "food" and "drinks" folders below are controller. there are only two controllers which contain all of the sub-items in them.
ie in the first line of the example, controller=food, method=asian
in the second line controller=food, method=pad-thai and so on and so forth.
www.mysite.com/food/asian/
www.mysite.com/food/asian/pad-thai
www.mysite.com/food/italian/chicken-parmigiana
www.mysite.com/drinks/cocktails/bloody-mary
how would i write routes so that www.mysite.com/food/asian/pad-thai will direct to the food controller and the paid thai method within that controller, and also have a rule to send from www.mysite.com/food/asian/ to the food controller and asian index method??
The MVC design pattern isn't for rewriting URLs to point to folder structures. It can do this but it certainly isn't its main purpose. If you're trying to create a URL structure with static content, it might be easier to use the URL rewriting functionality built into IIS.
If you're creating a full MVC application, set up FoodController and DrinkController to serve up your views, for example:
public class FoodController : Controller
{
public ActionResult ViewDishByTag(string itemType, string itemTag)
{
// If an itemType is displayed without itemTag, return an 'index' list of possible dishes...
// Alternatively, either return a "static" view of your page, e.g.
if (itemTag== "pad-thai")
return View("PadThai"); // where PadThai is a view in your shared views folder
// Alternatively, look up the dish information in a database and bind return it to the view
return ("RecipeView", myRepo.GetDishByTag(itemTag));
}
}
Using the example above, your route might look a little like this:
routes.MapRoute(
"myRoute",
"{controller}/{itemType}/{itemTag}",
new
{
controller = UrlParameter.Required,
action = "ViewDishByTag",
itemtype = UrlParameter.Optional,
itemTag = UrlParameter.Optional
}
);
Your question doesn't contain much detail about your implementation, so if you'd like me to expand on anything, please update your question.

ASP.NET MVC 3 Razor Partial View - jQuery Included In Main Layout

A partial view that I'm using requires certain jQuery libraries to be included, and I'm currently trying to think of the best way to nicely add them.
My current setup is as follows:
_Layout.cshtml:
...
#if (ViewBag.jQuery)
{
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")"
type="text/javascript"></script>
}
...
Index.cshtml:
...
#{ Html.RenderPartial("PartialView",
new MVCModel.Models.MyModel(),
new ViewDataDictionary { { "ViewBag", ViewBag } }); }
...
PartialView.cshtml:
...
#{
if (ViewBag.ViewBag != null)
{
var vb = (dynamic)ViewBag.ViewBag;
vb.jQuery = true;
}
}
...
So what I'm trying to do is "turn on" the library in the partial view, and have that boolean propagate up to the master layout. This makes it so that I can enable the library as much as I want wherever I want (many partial views, or one view used multiple times), but it will only get included once. On top of that, I get control over the ordering of the includes, so I can make sure a files dependencies are included first.
I'm just wondering if there is a nicer way of pulling this off.
Right now my two biggest issues are:
The ViewBag is not strongly typed, so intellisense won't tell me which libraries are available to me.
Passing the ViewBag to the partial view, just so I can re-use it.
Here is a simple way to accomplish your objective.
There is a function in MVC Razor views called RenderSection. It has a syntax like
#RenderSection ("occasionalScripts", false)
(occasionalScripts is the name of the section and false means the section is optional and may not appear in every page.)
You would want to include this in your _Layout.cshtml which is Views\Shared. This will allow your main script template to display a section with your script definitions if you have it defined for a particular view.
Generally, you want to put this at the bottom of your page just before the closing </body> tag. (This is a best practice for performance.) I would list all of my other scripts (the ones which load on every page) just above it. The reason is because the load order of jQuery scripts is very important.
In each of your views where you have special scripts, add the following:
#section occasionalScripts {
... put script references here
}
If you have a view which requires no special scripts, then you don't need to include this section there. The false on the #RenderSection tag will accommodate any view where the #section tag is missing.
On each page where this functionality is implemented, you can select different scripts.
Optionally, you could have multiple sections defined for different categories of files.
I usually have a different approach, and consider that ALL libraries used on the web site should be referenced on every page. It does make the first load a bit slower, but then the loading of all the following pages is faster, as it uses the browser-cache.
To improve it even better, you can make all your libraries available in only one file.
I used SquishIt for that at some point. I integrates rather well in ASP.NET MVC.

ASP.Net MVC: Showing the same data using different layouts

I'm wanting to create a page that allows the users to select how they would like to view their data - i.e. summary (which supports grouping), grid (which supports grouping), table (which supports grouping), map, time line, xml, json etc.
Now each layout would probably have different use a different view model, which inherit from a common base class/view model. The reason being that each layout needs the object structure that it deals with to be different (some need hierarchical others a flatter structure).
Each layout would call the same repository method and each layout would support the same functionality, i.e. searching and filtering (hence these controls would be shared between layouts). The main exception to this would be sorting which only grid and table views would need to support.
Now my question is given this what do people think is the best approach.
Using DisplayFor to handle the rendering of the different types?
Also how do I work this with the actions... I would imagine that I would use the one action, and pass in the layout types, but then how does this support the grouping required for the summary, grid and table views.
Do i treat each grouping as just a layout type
Also how would this work from a URL point of view - what do people think is the template to support this layout functionality
Cheers
Anthony
Conceptually, the View is the part of an MVC web app that is responsible for handling the displaying of data. So, if you want to display data in different ways, it seems most logical that each "display" has its own corresponding aspx View.
All of the View Models can inherit from the same base model. So, for example, we might have four models and three views:
public abstract class BaseViewModel {}
public class GridViewModel : BaseViewModel {}
public class TableViewModel : BaseViewModel {}
public class SummaryViewModel : BaseViewModel {}
GridViewPage<GridViewModel>
TableViewPage<TableViewModel>
SummaryViewPage<SummaryViewModel>
Each of the views can have different stylehsheets and javascript files attached, so you should be able to use DisplayFor if you'd like, or you can create the layout by hand.
As for the controller, you can create one action method that returns any of the three views, or you could create three separate ActionResults, one for each view. Here's the "monolithic" ActionResult:
public ActionResult PageViewResult(string pageType)
{
switch (pageType)
{
//define your cases, return your views and your models
//make sure to set a default
}
}
You can format the routes however you see fit. For example, with the above "monolithic" ActionResult, we could create the following route in our Global.asax file:
routes.MapRoute(
"FormattedViewPage", // Route name
"View/Page/{pageType}", // URL with parameters
new { controller = "ViewPageController", action = "PageViewResult", pageType = "grid" } // Parameter defaults
);
Hope this helps. Let me know if you have any questions.
First of if it's the same data then I would try to use the same model for all the views but either use different css or different aspx pages/controls for rendering the model depending on how the user wants to view the data.
Also you may want to look into how much of this can be done in JavaScript, it all depends on how rich you want the user experience to be.

Resources