Sitecore LinkManager GetItemUrl. Why its so tricky? - c#

I have task to get content's url when smth is changed on website. It like CRUD operation logging (In my case i am logging that urls to other system to further processing). It should work on version 6 and higher.
When i started it seems pretty simple subscribe to event then take item and generate url for it. I subscribed to two events publish:itemProcessing (because only here item is not yet removed from web database), publish:itemProcessed (for add and update).
This events give me object of time Item, so it seems to get url pretty simple like that
var options = LinkManager.GetDefaultUrlOptions();
options.AlwaysIncludeServerUrl = true;
options.SiteResolving = true;
var url = LinkManager.GetItemUrl(item, options);
And here my problem starts. First i need to have right url and the same way as it is generated on website but here url returns me smth like "http://domain/sitecore/content/Home.aspx".
So I added new methods to find right site from site definitions
private List<KeyValuePair<string, SiteContext>> GetSites()
{
return SiteManager.GetSites()
.Where(
s =>
!string.IsNullOrEmpty(s.Properties["rootPath"]) &&
!string.IsNullOrEmpty(s.Properties["startItem"]))
.Select(
d => new KeyValuePair<string, SiteContext>($"{d.Properties["rootPath"]}{d.Properties["startItem"]}",
new SiteContext(new SiteInfo(d.Properties))))
.ToList();
}
public virtual SiteContext GetSiteContext(Item item)
{
var site = _sites.LastOrDefault(s => item.Paths.FullPath.ToLower().StartsWith(s.Key.ToLower()));
return site.Value;
}
options.Site = GetSiteContext(Item item);
Again issue is not solved because sitecore returns "http://127.0.0.1/en.aspx"
Then i continue reading and understood that site definition should have targetHostName (it actually make sense since one site can have multiple domains) but when i add targetHostName now it returns me other link "://targetHostName/en.aspx" so http|https is missing. Second problem is that it returns me EN.aspx which means that this page can be accessible throw http://targetHostName/en.aspx and http://targetHostName
Now i have following site definitions
<sites>
<site name="shell" virtualFolder="/sitecore/shell" physicalFolder="/sitecore/shell" rootPath="/sitecore/content" startItem="/home" language="en" database="core" domain="sitecore" loginPage="/sitecore/login" content="master" contentStartItem="/Home" enableWorkflow="true" enableAnalytics="false" analyticsDefinitions="content" xmlControlPage="/sitecore/shell/default.aspx" browserTitle="Sitecore" htmlCacheSize="2MB" registryCacheSize="3MB" viewStateCacheSize="200KB" xslCacheSize="5MB" />
<site name="login" virtualFolder="/sitecore/login" physicalFolder="/sitecore/login" enableAnalytics="false" database="core" domain="sitecore" disableXmlControls="true" />
<site name="admin" virtualFolder="/sitecore/admin" physicalFolder="/sitecore/admin" enableAnalytics="false" enableWorkflow="true" domain="sitecore" loginPage="/sitecore/admin/login.aspx" />
<site name="service" virtualFolder="/sitecore/service" physicalFolder="/sitecore/service" />
<site name="modules_shell" virtualFolder="/sitecore modules/shell" physicalFolder="/sitecore modules/shell" rootPath="/sitecore/content" startItem="/home" language="en" database="core" domain="sitecore" content="master" enableAnalytics="false" enableWorkflow="true" />
<site name="modules_website" virtualFolder="/sitecore modules/web" physicalFolder="/sitecore modules/web" rootPath="/sitecore/content" startItem="/home" language="en" database="web" domain="extranet" allowDebug="true" cacheHtml="true" />
<site name="website" hostName="sitecore6.target|sitecore6.local" targetHostName="sitecore6.target" schema="http" virtualFolder="/" physicalFolder="/" rootPath="/sitecore/content" startItem="/home" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
<site name="scheduler" enableAnalytics="false" domain="sitecore" />
<site name="system" enableAnalytics="false" domain="sitecore" />
<site name="publisher" domain="sitecore" enableAnalytics="false" enableWorkflow="true" />
</sites>
And link manager settings
<linkManager defaultProvider="sitecore">
<providers>
<clear />
<add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="true" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="asNeeded" languageLocation="filePath" lowercaseUrls="false" shortenUrls="true" useDisplayName="false" />
</providers>
</linkManager>

The problem is occurring because of where you are generating the link. When you have the AlwaysIncludeServerUrl option set to true, Sitecore will use the current Sitecore.Context.Site information to work out the server Url.
To set the http or https section, you need to add an attribute called scheme to your site definition - I think you just have a typo as you had one called schema:
<sites>
<site name="website" hostName="sitecore6.target|sitecore6.local" targetHostName="sitecore6.target" scheme="http" virtualFolder="/" physicalFolder="/" rootPath="/sitecore/content" startItem="/home" database="web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="10MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="5MB" filteredItemsCacheSize="2MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" />
</sites>
During a publish event, that Context.Site will be the shell website. So it will not pickup the targetHostName for your website definition.
To force that, you need to use a SiteContextSwitcher
var website = Sitecore.Configuration.Factory.GetSite("website");
using (new SiteContextSwitcher(website))
{
var options = LinkManager.GetDefaultUrlOptions();
options.AlwaysIncludeServerUrl = true;
options.SiteResolving = true;
var url = LinkManager.GetItemUrl(item, options);
}
Then the Url will be generated using the website's targetHostName and should generate how you are expecting.
Just one last note - best practice would be to patch the new Site definition via an include file rather than edit the main Sitecore config. Check out your include folder, there should be a SiteDefinition.config.example file in there. It shows you how to do it.

Related

Obfuscar - skip obfuscation of anonymous types

I'm using the open source obfuscation software "Obfuscar". Is there a way to configure it to not obfuscate the property names in my anonymous types?
I'm using RestSharp to send HTTP requests, and my Json body content is an anonymous type.
request.AddJsonBody(new {
data = new {
type = "attachments",
attributes = new {
name = "foo"
}
}
});
I would like it to NOT rename those properties like "data", "type" etc in the anonymous type, because renaming them affects the Json string that it gets serialized to.
In github obfuscar issues i found this answer and it worked for me
You need to create the node in Obfuscar xml configuration file:
<SkipType name="*AnonymousType*" skipProperties="true" skipMethods="true" skipFields="true" skipEvents="true" skipStringHiding="true" />
Example of full xml configuration file:
<?xml version="1.0" encoding="utf-8"?>
<Obfuscator>
<Var name="OutPath" value="C:\TMP" />
<AssemblySearchPath path="C:\Users\user\Documents\Projects\MyProject\bin\Release\net6.0" />
<AssemblySearchPath path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.0" />
<Module file="C:\Users\user\Documents\Projects\MyProject\bin\Release\net6.0\MyProject.dll">
<SkipType name="*AnonymousType*" skipProperties="true" skipMethods="true" skipFields="true" skipEvents="true" />
</Module>
<Var name="KeepPublicApi" value="false" />
<Var name="HidePrivateApi" value="true" />
</Obfuscator>
Next, you need to start obfuscar.exe -s "path_to_xml_configuration_file"
Also see the section in the Obfuscar documentation
https://docs.obfuscar.com/getting-started/configuration#exclusion-rules-by-configuration

IIS Website not autostarting

I'm struggling to get my IIS site to autostart. I'm using Quartz.Net inside it for nightly tasks but they aren't running because IIS disposes of it before they can run. I've attempted to set it to autostart and stay runninig by doing the following (using these instructions):
ApplicationHost.Config:
<configuration>
<configSections>
...
<system.applicationHost>
<applicationPools>
<add name="DefaultAppPool" enable32BitAppOnWin64="true" managedRuntimeVersion="v4.0" />
<add name="ASP.NET v4.0" enable32BitAppOnWin64="false" managedRuntimeVersion="v4.0" />
<add name="ASP.NET v4.0 Classic" managedRuntimeVersion="v4.0" managedPipelineMode="Classic" />
<add name="Classic .NET AppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Classic" />
...
<add name="AUTOSTARTSITE" autoStart="true" managedPipelineMode="Integrated" startMode="AlwaysRunning">
<processModel identityType="NetworkService" />
</add>
<applicationPoolDefaults managedRuntimeVersion="v4.0">
<processModel identityType="NetworkService" />
</applicationPoolDefaults>
</applicationPools>
...
<sites>
<site name="AUTOSTARTSITE" id="10" serverAutoStart="true" serviceAutoStartEnabled="true" serviceAutoStartProvider="StartUpCode">
<application path="/" applicationPool="AUTOSTARTSITE">
<virtualDirectory path="/" physicalPath="E:\websites\AUTOSTARTSITE" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:80:AUTOSTARTSITE.com" />
</bindings>
<traceFailedRequestsLogging enabled="true" />
<logFile directory="%SystemDrive%\inetpub\logs\LogFiles" />
</site>
<siteDefaults>
<logFile logFormat="W3C" directory="%SystemDrive%\inetpub\logs\LogFiles" />
<traceFailedRequestsLogging directory="%SystemDrive%\inetpub\logs\FailedReqLogFiles" />
</siteDefaults>
<applicationDefaults applicationPool="DefaultAppPool" />
<virtualDirectoryDefaults allowSubDirConfig="true" />
</sites>
<serviceAutoStartProviders>
<add name="StartUpCode" type="StartUpCode, AUTOSTARTSITE" />
</serviceAutoStartProviders>
<webLimits />
</system.applicationHost>
...
And here is my startup code. I didn't put in a namespace, and I have it log that it runs so I can confirm the process is working. Unfortunatly, it does not run.
StartUpCode:
public class StartUpCode : System.Web.Hosting.IProcessHostPreloadClient
{
readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public void Preload(string[] parameters)
{
SetupJobs();
logger.Info("Quartz Jobs Setup Successfully");
}
}
Despite these changes, it runs the same as before. Am I missing something obvious?
I think for all this stuff to work properly you need to install Application Initialization feature for IIS:
You can find more details on how it is supposed to work here.
We found generally that no matter what sometimes it just does not work. So there are really 2 solutions here:
Do not run scheduler in IIS, use Windows Service or scheduled task maybe
Write a pinger that will again be either windows service or scheduled task. It's probably one line with powershell to issue a GET to your site. That can run again as a service or scheduled task.
I would definitely prefer option 1 to not depend on IIS life-cycle for critical scheduling operations. Even though this approach with IIS hosting is still quite popular and we use it there is just too many problems with it in my experience.
We too had same issue using Quartz the reason was the IIS pool was shutting down after some idle time.So we had to prevent it from shutting down and it worked for Us.
Find more details here

Environment variables not being used when debugging through a Service Fabric project

When creating an ASP.NET Core app an environment variable called ASPNETCORE_ENVIRONMENT=Development will be set for you and when debugging you will see that the IHostingEnvironment is set to Development.
The problem is that when I use the same project in a solution set up for Service Fabric the environment variables don't seem to get injected and IHostingEnvironment just returns "Production".
How can I resolve this?
Note: I've set a breakpoint in the startup class to observe the IHostingEnvironment variable.
Reference for this answer: https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-manage-multiple-environment-app-configuration
I ran into the same issue with the default template. The following is similar to Duncan's answer but with two important differences: 1) You will not have to change any template code within the service, and 2) the IHostingEnvironment will be properly set.
First, add the ASPNETCORE_ENVIRONMENT variable to the <CodePackage> element of the PackageRoot\ServiceManifest.xml file of the application service:
<CodePackage Name="Code" Version="1.0.0">
<EntryPoint>
<ExeHost>
<Program>MyService.exe</Program>
<WorkingFolder>CodePackage</WorkingFolder>
</ExeHost>
</EntryPoint>
<EnvironmentVariables>
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value=""/>
</EnvironmentVariables>
</CodePackage>
As in Duncan's response, there are two changes you'll make to the ApplicationManifest.xml of your Service Fabric Application project. First, setup a parameter (variable) so that it can be modified when the ApplicationParameters files are substituted based on the way you deploy the project. Then, add an EnvironmentalOverrides section to your ServiceManifestImport element. The results of the two additions will look something like this:
<Parameters>
<Parameter Name="MyService_InstanceCount" DefaultValue="-1" />
<Parameter Name="AspNetCoreEnvironment" DefaultValue="" />
</Parameters>
<ServiceManifestImport>
<ServiceManifestRef ServiceManifestName="MyServicePkg" ServiceManifestVersion="1.0.0" />
<EnvironmentOverrides CodePackageRef="Code">
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value="[AspNetCoreEnvironment]" />
</EnvironmentOverrides>
</ServiceManifestImport>
Finally, you can add in the proper values in the individual ApplicationParameters files:
<Parameters>
<Parameter Name="MyService_InstanceCount" Value="-1" />
<Parameter Name="AspNetCoreEnvironment" Value="Development" />
</Parameters>
At this point, you can remove the variable from your service's Properties - Debug environmental variables.
I ran into the same issue and was able to create a solution that worked for me.
If you look at your ASP.NET Core project, you should see a Program.cs file. At the bottom of it you should see the following interface implementation:
Task<string> ICommunicationListener.OpenAsync(CancellationToken cancellationToken)
{
...
}
You're going to first want to change it to something like the following:
Task<string> ICommunicationListener.OpenAsync(CancellationToken cancellationToken)
{
var context = FabricRuntime.GetActivationContext();
var endpoint = context.GetEndpoint(_endpointName);
var config = context.GetConfigurationPackageObject("Config");
var environment = config.Settings.Sections["Environment"].Parameters["ASPNETCORE_ENVIRONMENT"].Value;
var serverUrl = $"{endpoint.Protocol}://{FabricRuntime.GetNodeContext().IPAddressOrFQDN}:{endpoint.Port}";
_webHost = new WebHostBuilder().UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseEnvironment(environment)
.UseUrls(serverUrl)
.Build();
_webHost.Start();
return Task.FromResult(serverUrl);
}
The key portion is the .UseEnvironment(environment) call, along with the supporting retrieval of the environment from the configuration. This will give ASP.NET Core the necessary information it needs to choose the environment.
Having done this, you'll obviously need to add the ASPNETCORE_ENVIRONMENT setting to the config section. That looks like the following:
Under your ASP.NET Core project you'll find a directory called PackageRoot/Config. Inside of that there should be a Settings.xml file. Add the following code inside the <Settings> tag...
<Section Name="Environment">
<Parameter Name="ASPNETCORE_ENVIRONMENT" Value="" />
</Section>
Next, you're going to want to look at the ApplicationPackageRoot/ApplicationManifest.xml file inside the actual Service Fabric Project (this is NOT the ASP.NET Core project). Two file changes are required.
Add the ASPNETCORE_ENVIRONMENT parameter inside the <Parameters> tag at the top of the file like so:
<Parameter Name="ASPNETCORE_ENVIRONMENT" DefaultValue="" />
Modify your <ServiceManifestImport> tag to include a <ConfigOverrides> section like so:
<ConfigOverrides>
<ConfigOverride Name="Config">
<Settings>
<Section Name="Environment">
<Parameter Name="ASPNETCORE_ENVIRONMENT" Value="[ASPNETCORE_ENVIRONMENT]" />
</Section>
</Settings>
</ConfigOverride>
</ConfigOverrides>
Finally, modify your ApplicationParameters/Local.1Node.xml and friends to contain the ASPNETCORE_ENVIRONMENT parameter:
<Parameter Name="ASPNETCORE_ENVIRONMENT" Value="Development" />
It's a lot of steps to add a freaking variable you can retrieve, but it does allow you a great deal of flexibility and follows the standard Service Fabric pattern to make deployments simple. I hope this helps!
The answer from Duncan worked for me, but there is a small variation for me, maybe due to the version of ASP.NET Core and Service Fabric I use.
I need to override the method CreateServiceInstanceListeners in my Web Stateless Service.
So that means I'll have this code:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new WebListenerCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
var environment = FabricRuntime.GetActivationContext()
?.GetConfigurationPackageObject("Config")
?.Settings.Sections["Environment"]
?.Parameters["ASPNETCORE_ENVIRONMENT"]?.Value;
return new WebHostBuilder().UseWebListener()
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseEnvironment(environment)
.UseApplicationInsights()
.UseUrls(url)
.Build();
}))
};
}
Of course, you need to set the ASPNETCORE_ENVIRONMENT variable as explained by Duncan.

Trusting unknown certificates in windows store app [duplicate]

I am trying to override the certificate validation in a Windows Store App to accept a self-signed certificate on two external services (using HttpClient) to allow the Windows 8 app to accept the certificates and establish a trust relationship for SSL
EDIT:
I implemented the approach documented here : Installing certs by using the appmanifest
and added the relevant .cer files to my application and ensured they are 'Content' and 'Copy Always'.
My package.appxmanifest Extensions section looks like this:
<Extensions>
<Extension Category="windows.certificates">
<Certificates>
<Certificate StoreName="TrustedPeople" Content="Assets\ReportingServices.cer" />
<Certificate StoreName="TrustedPeople" Content="Assets\Crm.cer" />
<Certificate StoreName="CA" Content="Assets\DigiCertHighAssurance.cer" />
<TrustFlags ExclusiveTrust="true" />
<SelectionCriteria AutoSelect="true" />
</Certificates>
</Extension>
but this still does not work.
I have tried putting the app certificates in the 'Root' StoreName but still no success. Does anyone have any ideas why this might not work please?
This is a bit of old one, but seeing as there are quite a few watchers I will give my solution.
// Create the httpClient and send the request
HttpBaseProtocolFilter aHBPF = new HttpBaseProtocolFilter();
// If you want to ignore expired Certs
aHBPF.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
// Untrused because this is a self signed cert that is not installed
aHBPF.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
// Host names and certs names may not match
aHBPF.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
HttpClient httpClient = new HttpClient(aHBPF);
HttpResponseMessage response = await httpClient.SendRequestAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token);
Just to save your time. I got to resolve this for 2 days of trial and error. Here you can solve it.
Add the .cer file to your project, Make the build action as "Content", copy as newer
then add this to your app manifest
<Capabilities>
<Capability Name="sharedUserCertificates" />
<Capability Name="enterpriseAuthentication" />
<Capability Name="privateNetworkClientServer" />
<Capability Name="internetClient" />
</Capabilities>
<Extensions>
<Extension Category="windows.certificates">
<Certificates>
<Certificate StoreName="Root" Content="Certificates\vibeapi.cer" />
<TrustFlags ExclusiveTrust="true" />
<SelectionCriteria AutoSelect="true" />
</Certificates>
</Extension>
</Extensions>
and to your code behind you can now access the file using this
//Testing https connection
HttpClientHandler msgHandler = new HttpClientHandler();
using (System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient(msgHandler, true))
{
var HTTPSURL = new Uri("https://www.sample.net/");
var response = await httpClient.GetAsync(HTTPSURL);
var responseStr = await response.Content.ReadAsStringAsync();
}
see link for reference
help
It will work if you put cer file to the project root and change Content section in manifest file to Content="file.cer"

create your own settings in xml

I'm in a ASP.NET project where I need to give several parameters to the administrator that is going to install the website, like:
AllowUserToChangePanelLayout
AllowUserToDeleteCompany
etc...
My question is, will be a good thing to add this into the web.config file, using my own configSession or add as a profile varibles? or should I create a XML file for this?
What do you do and what are the cons and favs?
I originally thought about web.config but I then realized that I should mess up with Website configurations and my own web app configuration and that I should create a different file, them I read this post and now I'm on this place... should I do this or that?
I usually use Settings - available via the project properties - Settings. These can be edited and saved in code, and I write a form / web page to edit them.
If you want to use the XML configuration, there's an attribute called file that reads external files.
You could have a web.config file and a someothername.config file. The someothername.config would have settings like:
<appSettings>
<add key="ConnString" value="my conn string" />
<add key="MaxUsers" value="50" />
</appSettings>
And the web.config would have
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings file="ExternalWeb.config">
<add key="MyKey" value="MyValue" />
</appSettings>
</configuration>
See DevX for the example I stole.
just to let you guys know that I did what configurator recommended but with a twist.
instead of asking all the time (that I need) for
System.Configuration.ConfigurationManager.AppSettings["myKey"];
I just created a static class that would pull this values with what we call by Strongly typed values (so you don't need to remember all the values)
the mySettings class
public static class mySettings
{
public enum SettingsType
{ UserPermitions, WebService, Alerts }
public enum SectionType
{ AllowChangeLayout, AllowUserDelete, MaximumReturnsFromSearch, MaximumOnBatch, SendTo }
public static String GetSettings(SettingsType type, SectionType section)
{
return
ConfigurationManager.AppSettings[
String.Format("{0}_{1}",
Enum.Parse(typeof(SettingsType), type.ToString()).ToString(),
Enum.Parse(typeof(SectionType), section.ToString()).ToString())
];
}
}
the web.config appSettings part
<configuration>
<appSettings file="myApp.config">
<add key="UserPermitions_AllowChangeLayout" value="" />
<add key="UserPermitions_AllowUserDelete" value="" />
<add key="WebService_MaximumReturnsFromSearch" value="" />
<add key="Alerts_SendTo" value="" />
<add key="Alerts_MaximumOnBatch" value="" />
</appSettings>
</configuration>
the entire myApp.config file
<?xml version="1.0" encoding="utf-8" ?>
<!--
###
### This file serves the propose of a quick configuration.
### Administrator can either change this values directly or use the
### Settings tab in the application.
###
-->
<appSettings>
<!-- *** User Access Configuration *** -->
<!-- Allow user to change the panels layout {1: Yes} {0: No} -->
<add key="UserPermitions_AllowChangeLayout" value="1" />
<!-- Allow user to delete a company fro monitoring -->
<add key="UserPermitions_AllowUserDelete" value="1" />
<!-- *** Web Service configuration *** -->
<!-- Maximum responses from the search service -->
<add key="WebService_MaximumReturnsFromSearch" value="10" />
<!-- *** Allerts configuration *** -->
<!-- Send the alerts to the email writeen below -->
<add key="Alerts_SendTo" value="bruno.in.dk#gmail.com" />
<!-- Send an alert when user import more than the number bellow -->
<add key="Alerts_MaximumOnBatch" value="10" />
</appSettings>
So, now I call like this:
p.value = mySettings.GetSettings(
mySettings.SettingsType.WebService,
mySettings.SectionType.MaximumReturnsFromSearch);
Hope that helps someone with the same problem :)
You may also put your configurations in a settings file. In your project, open Properties and go to Settings which looks
like so
To access the values in your code, use Properties.Settings.YourSettingName;
Use Properties.Settings.Default.Reload(); to refresh your settings during runtime

Categories

Resources