ASP.NET MVC 5 custom RazorViewEngine for multiple portal structure - asp.net-mvc-5

I setup my MVC 5 site by category, then controller, model, view sub-folders in each category, i.e. root folder folders \Home and \Products would have these three sub-folders as well as a root \Shared\Views folder. I followed a terrific article my Matthew Renz, Clean Architecture in ASP.NET MVC 5. Done in part by creating a custom RazorViewEngine, specifically:
public CustomRazorViewEngine()
{
ViewLocationFormats = new string[]
{
"~/{1}/Views/{0}.cshtml",
};
PartialViewLocationFormats = new string[]
{
"~/Shared/Views/{0}.cshtml"
};
}
There aren't many changes beyond that. I was wondering if I could build on this idea and setup a website project with a \Portals root folder and sub-folders for each Portal using some identifier (name or number) - similar to DNN. The changes to the custom razor view engine code might look some like:
public CustomRazorViewEngine()
{
ViewLocationFormats = new string[]
{
"~/Portals/{2}/{1}/Views/{0}.cshtml",
};
PartialViewLocationFormats = new string[]
{
"~/Portals/{2}/Shared/Views/{0}.cshtml"
};
}
I am not sure where the values {0} and {1} come from, however. I could find a means for obtaining {2}, the portal website name. The relative paths for the rest of the site, such as \Content, \Scripts, etc. I believe I could structure myself.
The purpose for this approach is to deliver to the client a solution in which common code can be reused to support a number of portals with unique skins and features. Thank you for your time and consideration and let me know if you have any questions.
John

These are placeholders in the string that can be used to put the area name, controller name or action name into the string by the controller. {2} is area, {1} is controller,{0} is the action.
You may also be interested to know that when using Asp.Net Core it's easy to get the standard Razor View Engine to locate views and such in custom locations via a ViewLocationExpander rather than needing to create a new view engine that inherits from the Razor View Engine. I only mention this because you added the asp.net-core-mvc tag on your question.
Here is a stack overflow answer that shows how:
How to specify the view location in asp.net core mvc when using custom locations?

Related

asp.net mvc area not working on iis

I crated area/modular MVC5 application according to this tutorial:
This
it worked perfectly on local. but i got 404 error, when i deployed project on iis and clicked on specified area link.
and i didn't find any solution for that.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Sale
and this is my action links which are perfectly working on local run:
#Html.ActionLink("Sale","Index","Sale",new { Area="Sale"},null)
edited:
public class SaleAreaRegistration:AreaRegistration
{
public override string AreaName
{
get
{
return "Sale";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Sale_default",
"Sale/{controller}/{action}/{id}",
new { controller = "Sale", action = "Index", id = UrlParameter.Optional },
new string[] { "MVCPluggableDemo.Controllers" }
);
}
}
attention: according to the tutorial which i said in first line. i created my module project in another virtual project in solution(the real path is in area folder of main project like"~/ProjectName/Areas/"). but i think the publisher does't understand it...because i don't see any of my module views in deployed path.
its better to completing my help request by this obvious question:
How to deploy MVC project include areas, and the modules which are in areas folder?
Its simply obvious:
Check your page directory. The server cannot locate your the file,
it maybe in different folder.
Also try to Check this.
the main Reason of my problem was this:
VisualStudio doesn't include my Area folder, in publish path..why? i don't know
Solution: no solution yet!..but i added my module dll inside bin folder manually. and created my areas folder and his modules projects solution(dlls' are not not necessary) in main published solution, manually to ..
finally it worked, but i dont thing is the standard solution

Creating more than one site in Kentico?

Am going to use Kentico to create more than one store (Site) and assign user for each store to add/modify/delete his products, i've created 2 stores the first one with domain localhost:8080 and second one is storeone.localhost:8080 as documentation said in Kentico Doc URL, i can open first site with no problem but when i tried to switch to second Site it gives me Bad Request - Invalid Hostname .. can any one help me in this?? .. also i would appreciate it if any one help me on how to extract product data using Kentico API's as documentation provide me only with updating/modifying/removing data from database and i want to know how to display it with it's attachments like images pdf that i've uploaded it.
The best approach is to use two different ports. The reason for this is IIS is by default bound to port 80. So what I'd do is leave one site at 80 and do another at say 2. Make these bindings in IIS then go to Kentico and add your second site at localhost:2 vs. :8080. There's a conflict with port numbers. Kentico and IIS are "confused" and don't know which one to serve up. The only way it will work with the same port is to start and stop sites within Kentico.
Brenden is correct - there cannot be 2 sites running on same domain. What you need to do is configure IIS bindings. What I often do is that I configure my hosts file (C:\Windows\System32\drivers\etc) and add a few more rules like:
127.0.0.1 localhost2
127.0.0.1 localhost3
And then I can use bind my Kentico sites to these domains. Don't forget to also change the domain names in Kentico -> Sites app.
As for your second question:
It depends whether you want to get only SKUInfo object or page object where the custom data (page type fields) are stored. If you just need SKUInfo you can use something like:
// gets only corresponding SKU Info object
var singleProduct = SKUInfoProvider.GetSKUInfo(1); // SKUID from COM_SKU table
if (singleProduct != null)
{
var name = singleProduct.SKUName;
var price = singleProduct.SKUPrice;
}
If you need to get the product with all custom fields you need to use the Pages API as you would with any other page. A simple example:
// gets sku with all custom properties
var tree = new TreeProvider(MembershipContext.AuthenticatedUser);
var singleProduct = tree.SelectSingleDocument(2); // DocumentID from CMS_Document table
if (singleProduct != null)
{
// work with product
}
// or for multiple products
var products = tree.SelectNodes("custom.myProductType");
foreach (var product in products)
{
// work with products/pages
}
For the purpose of retrieving pages I would highly recommend to check this documentation article which contains a lot of examples.

Deploy two connected webparts in a page layout during feature activation?

I've implemented 2 webparts (deriving from Microsoft.SharePoint.WebPartPages.WebPart, the WSS 3 WebPart), one of which is a provider and the other the consumer (implementing ASP.net connection model, with ConnectionProviderAttribute and ConnectionConsumerAttribute methods).
I managed to deploy them in a feature which also deploys a Page Layout containing two webpart zones, which are themselves populated during the FeatureAvtivated method of the feature receiver, with the 2 newly created webparts. All of this works just fine.
For information, I used this link to make it work. Beware, the method using AllUsersWebPart tag in elements.xml, shown in links like this one (http://www.andrewconnell.com/blog/archive/2007/10/07/Having-Default-Web-Parts-in-new-Pages-Based-Off-Page.aspx) work, but if you deactivate, then reactivate your feature, you just have double webparts in your future pages based on the layout. The method described here (http://sharepoint.coultress.com/2008/06/adding-web-part-to-page-layout.html) just threw me an error when analysing metadata for the layout aspx file (the problem seemed to come from the line in the ZoneTemplate tag).
My next goal is to connect these webparts together right after all this, thus enabling the end user to create pages, based on the layout, containing by default the two webparts connected together (right now everything works except for the connected part).
I tried something like this, using ASP.net connection model (the other one, WSS model, throws logically an error because I'm not implementing the good interfaces). But even though the connection resulting from the "mgr.SPConnectWebParts()" method doesn't throw any exception and actually adds the connection to the connection list of the webpart manager, I can see in debug mode that the connection property 'IsActive" is false (maybe normal), and that when I create a new page based on the layout, the webparts appear not connected.
Any guess? I believe there's something with the fact that the webparts cannot be connected before the page containing them is actually created, but I'm far from sure of it.
Declarative web part connection provisioning is actually quite straightforward:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="Module1">
<File Path="Module1\default.aspx" Url="demo.aspx">
<AllUsersWebPart ID="testProvider">...</AllUsersWebPart>
<AllUsersWebPart ID="testConsumer">...</AllUsersWebPart>
<WebPartConnection ID="testConnection"
ProviderID="testProvider"
ProviderConnectionPointID="providerID"
ConsumerID="testConsumer"
ConsumerConnectionPointID="consumerID" />
</File>
</Module>
</Elements>
Details:
http://blogs.code-counsel.net/Wouter/Lists/Posts/Post.aspx?ID=161
You can find connection point IDs with PowerShell if you first connect your web parts manually:
$web = Get-SPWeb <WebURL>
$wpman = $web.GetLimitedWebPartManager("<PageURL>", [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
$wpman.SPWebPartConnections
Tried creating the web parts on the page programmatically? You'll have far fewer headaches than trying to do it declaratively.
Look up the SPLimitedWebPartManager class for how to handle web parts on a provisioned page.
Also, web parts in a web part zone are tied to the URL of the page on which they are added. This is by design of the the ASP.NET Web Part Manager.
Thus, if you added web parts to zones on a page layout at directory: http://webapp/sites/site/_catalog/master/mypagelayout.aspx - the web parts will ONLY appear on that page. Createa new page at /sites/site/Pages/MyPage.aspx and the web parts you added before won't appear. The workaround for this is to explicitly add web parts not within web part zones, and this can only be done in an authored page layout (usually in SharePoint Designer).
If the web parts are static in the page layout (and you want them to show in every page) then this is actually easier for you to deploy - just maintain the layout in your source, and have it provisioned via a Module element.
Finally I used another approach to reach my goal. In the OnLoad event of the provider webpart, I check if my page is in edit/new mode, and then check if the page contains the consumer webpart (via the webpartmanager) and if they are not already connected. If this is the case, I connect them.
The code to connect permanently the webparts:
private void SetUpConnections()
{
SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite siteContext = new SPSite(SPContext.Current.Site.ID))
using (SPWeb webContext = siteContext.OpenWeb(siteContext.ServerRelativeUrl))
using (SPLimitedWebPartManager spManager = webContext.GetFile(SPContext.Current.File.Url).GetLimitedWebPartManager(PersonalizationScope.Shared))
{
foreach (Microsoft.SharePoint.WebPartPages.WebPart consumer in spManager.WebParts)
{
if (consumer is MyConsumerWebPart)
{
bool alreadyConnected = false;
Microsoft.SharePoint.WebPartPages.WebPart provider = spManager.WebParts[this.ID] as Microsoft.SharePoint.WebPartPages.WebPart;
foreach (SPWebPartConnection connection in spManager.SPWebPartConnections)
{
if (connection.Provider == provider && connection.Consumer == consumer) { alreadyConnected = true; break; }
}
if (!alreadyConnected)
{
// Connects webparts permanently (but the page would need a reload to display the connection)
ProviderConnectionPoint providerConnectionPoint = spManager.GetProviderConnectionPoints(provider)["MyConnectionProviderInterfaceId"];
ConsumerConnectionPoint consumerConnectionPoint = spManager.GetConsumerConnectionPoints(consumer)["MyConnectionConsumerInterfaceId"];
spManager.SPConnectWebParts(provider, providerConnectionPoint, consumer, consumerConnectionPoint);
// Connects webparts locally (for current edit mode)
SPWebPartManager currentSPManager = WebPartManager.GetCurrentWebPartManager(this.Page) as SPWebPartManager;
System.Web.UI.WebControls.WebParts.WebPart currentProvider = this;
System.Web.UI.WebControls.WebParts.WebPart currentConsumer = currentSPManager.WebParts[consumer.ID];
ProviderConnectionPoint currentProviderConnectionPoint = currentSPManager.GetProviderConnectionPoints(currentProvider)["SearchBarProvider"];
ConsumerConnectionPoint currentConsumerConnectionPoint = currentSPManager.GetConsumerConnectionPoints(currentConsumer)["SearchBarConsumer"];
currentSPManager.SPConnectWebParts(currentProvider, currentProviderConnectionPoint, currentConsumer, currentConsumerConnectionPoint);
}
}
}
}
});
}
The code to check if the page is in new/edit mode:
if (SPContext.Current.FormContext.FormMode == SPControlMode.New
|| SPContext.Current.FormContext.FormMode == SPControlMode.Edit)
{
this.SetUpConnections();
}

Install ClientAccessPolicy.xml to Default Web Site using Wix

I'm using Wix to install my web application, and it includes a Silverlight app. Because of cross-domain restrictions, I need to install a ClientAccessPolicy file to ensure that the Silverlight app can talk to the included web services.
Unfortunately, ClientAccessPolicy.xml has to be available from the root of the site, so I can't just place it with my web services or web site. e.g.
Works: http://someserver/ClientAccessPolicy.xml
Doesn't work: http://someserver/MyApp/ClientAccessPolicy.xml
How can I find the directory for the IIS "Default Web Site" to copy the file there as part of the install?
Unfortunately, you have to author a custom action for this. It seems to be just a simple immediate action, which is to find the correct directory path and put it to a property.
UPDATE: The sample C# code for this might look like this:
DirectoryEntry website = new DirectoryEntry(string.Format("IIS://localhost/w3svc/{0}/Root", siteID));
if (website != null)
{
string sitePath = website.InvokeGet("Path") as string;
if (sitePath != null)
{
session["SITE_PATH"] = sitePath;
return ActionResult.Success;
}
}
return ActionResult.Failure;
It assumes that you know the siteID in some way. If it's not always default web site, it is better to let the user choose, for instance. But that's another story.
Note also that this code requires special privileges to access DirectoryEntry - the regular user is not enough.
Hope this helps.

WSS 3.0 Site Provisioning

Is there any way to do WSS 3.0 site provisioning? My client's requirement is attributes as variables that will be defined in XML format: Organization Name, Logo, Address, User and Role information. The client should be able to install this web application to any WSS production server by just defining the attributes in the XML file.
Is it possible to to write a utility to parse that well defined XML and provision the site accordingly?
It's possible to provision sites from the object model, but creating entirely customized sites is beyond the scope of a single question. To get you started, you should take a look at the SPWebCollection.Add as well as the SPSiteCollection.Add.
To create a site collection and some subsites into one of your web applications, you could use something like this:
var farm = SPFarm.Local;
var solution = farm.Solutions.GetValue<SPSolution>("YourSolution.wsp");
var application = solution.DeployedWebApplications.First();
var sites = application.Sites;
using(var site = sites.Add("/", "Root Site", "Description", 1033, "YOURTEMPLATE#1", "YOURDOMAIN\SiteCollectionAdmin", "Site Collection Admin", "admin#yourcompany.example")) {
using(var rootWeb = site.RootWeb) {
// Code customizing root site goes here
using (var subSite = rootWeb.Webs.Add("SubSite", "Sub Site", "Description", 1033, "YOURTEMPLATE#2", false, false)) {
// Code customizing sub site goes here
}
}
}
Yes, there are more than one.
Take a look at SharePoint Solution Generator which is in Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions.
You may create a site with all requirements of yours (pages, lists, document libraries...) and then generate a VS project that will create a SharePoint feature with all of your site. Then you may deploy that feature to any WSS production server.
You may alter the VS project to implement the logic to read your attributes from an additional xml file.
If the structure of your site is plain or you can save it as a template you may also write a small console application that reads the attribute xml file and create the site.
Create a regular solution, or use the aforementioned solution generator to generate the .wsp file. Then create a small console application, that expects the variables you mentioned as parameters.
With the code listed above, provision the new sitecollection from that solution, and store the entered parameters (Company name etc.) in the site in a list, or in the SPSite.Properties propertybag, from which you can then read them in custom webparts etc..
The SharePoint Data Population Tool available on CodePlex allows you to define sites with XML.

Resources