This only clears items in the user cache:
public static void ClearCache()
{
foreach (DictionaryEntry entry in HttpRuntime.Cache)
{
HttpRuntime.Cache.Remove(entry.Key.ToString());
}
}
Is there any way to access the kernel cache as well?
Clarification: I want to print the keys of all items in the kernel cache, and as a bonus I'd like to be able to clear the kernel cache from a C# method as well.
Yep, it's possible to programmatically enumerate and remove items from IIS's kernel cache.
Caveats:
non-trivial text parsing requred for enumeration
lots of ugly P/Invoke required for removal
Also, you'll need at least Medium Trust (and probably Full Trust) to do the things below.
Removal won't work in IIS's integrated pipeline mode.
Enumeration probably won't work on IIS6
Enumeration:
The only documented way I know to enumerate the IIS kernel cache is a command-line app available in IIS7 and above (although you might be able to copy the NETSH helper DLL from V7 onto a V6 system-- haven't tried it).
netsh http show cachestate
See MSDN Documentation of the show cachestate command for more details. You could turn this into an "API" by executing the process and parsing the text results.
Big Caveat: I've never seen this command-line app actually return anything on my server, even for apps running in Classic mode. Not sure why-- but the app does work as I can see from other postings online. (e.g. http://chrison.net/ViewingTheKernelCache.aspx)
If you're horribly allergic to process creation and feeling ambitious, NETSH commands are implemented by DLL's with a documented Win32 interface, so you could write code which pretends it's NETSH.exe and calls into IIS's NETSH helper DLL directly. You can use the documentation on MSDN as a starting point for this approach. Warning: impersonating NETSH is non-trivially hard since the interface is 2-way: NETSH calls into the DLL and the DLL calls back into NETSH. And you'd still have to parse text output since the NETSH interface is text-based, not object-based like PowerShell or WMI. If it were me, I'd just spawn a NETSH process instead. ;-)
It's possible that the IIS7 PowerShell snapin may support this functionality in the future (meaning easier programmatic access than the hacks above), but AFAIK only NETSH supports this feature today.
Invalidation:
I've got good news and bad news for you.
The good news: Once you know the URL of the item you want to yank from IIS's kernel cache, there's a Win32 API available to remove it on IIS6 and above. This can be called from C# via P/Invoke (harder) or by putting the call in a managed C++ wrapper DLL. See HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK on MSDN for details.
I took a stab at the code required (attached below). Warning: it's ugly and untested-- it doesn't crash my IIS but (see above) I can't figure out how to get cache enumeration working so I can't actually call it with a valid URL to pull from the cache. If you can get enumeration working, then plugging in a valid URL (and hence testing this code) should be easy.
The bad news:
as you can guess from the code sample, it won't work on IIS7's integrated pipeline mode, only in Classic mode (or IIS6, of course) where ASP.NET runs as an ISAPI and has access to ISAPI functions
messing with private fields is a big hack and may break in a new version
P/Invoke is hard to deal with and requires (I believe) full trust
Here's some code:
using System;
using System.Web;
using System.Reflection;
using System.Runtime.InteropServices;
public partial class Test : System.Web.UI.Page
{
/// Return Type: BOOL->int
public delegate int GetServerVariable();
/// Return Type: BOOL->int
public delegate int WriteClient();
/// Return Type: BOOL->int
public delegate int ReadClient();
/// Return Type: BOOL->int
public delegate int ServerSupportFunction();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_GetServerVariable();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_WriteClient();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_ReadClient();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_ServerSupportFunction();
public static readonly int HSE_LOG_BUFFER_LEN = 80;
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public struct EXTENSION_CONTROL_BLOCK
{
/// DWORD->unsigned int
public uint cbSize;
/// DWORD->unsigned int
public uint dwVersion;
/// DWORD->unsigned int
public uint connID;
/// DWORD->unsigned int
public uint dwHttpStatusCode;
/// CHAR[HSE_LOG_BUFFER_LEN]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 80 /*HSE_LOG_BUFFER_LEN*/)]
public string lpszLogData;
/// LPSTR->CHAR*
public System.IntPtr lpszMethod;
/// LPSTR->CHAR*
public System.IntPtr lpszQueryString;
/// LPSTR->CHAR*
public System.IntPtr lpszPathInfo;
/// LPSTR->CHAR*
public System.IntPtr lpszPathTranslated;
/// DWORD->unsigned int
public uint cbTotalBytes;
/// DWORD->unsigned int
public uint cbAvailable;
/// LPBYTE->BYTE*
public System.IntPtr lpbData;
/// LPSTR->CHAR*
public System.IntPtr lpszContentType;
/// EXTENSION_CONTROL_BLOCK_GetServerVariable
public EXTENSION_CONTROL_BLOCK_GetServerVariable GetServerVariable;
/// EXTENSION_CONTROL_BLOCK_WriteClient
public EXTENSION_CONTROL_BLOCK_WriteClient WriteClient;
/// EXTENSION_CONTROL_BLOCK_ReadClient
public EXTENSION_CONTROL_BLOCK_ReadClient ReadClient;
/// EXTENSION_CONTROL_BLOCK_ServerSupportFunction
// changed to specific signiature for invalidation callback
public ServerSupportFunction_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK ServerSupportFunction;
}
/// Return Type: BOOL->int
///ConnID: DWORD->unsigned int
///dwServerSupportFunction: DWORD->unsigned int
///lpvBuffer: LPVOID->void*
///lpdwSize: LPDWORD->DWORD*
///lpdwDataType: LPDWORD->DWORD*
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public delegate bool ServerSupportFunction_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK(
uint ConnID,
uint dwServerSupportFunction, // must be HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK
out Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK lpvBuffer,
out uint lpdwSize,
out uint lpdwDataType);
public readonly uint HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK = 1040;
// typedef HRESULT (WINAPI * PFN_HSE_CACHE_INVALIDATION_CALLBACK)(WCHAR *pszUrl);
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public delegate bool Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK(
[MarshalAs(UnmanagedType.LPWStr)]string url);
object GetField (Type t, object o, string fieldName)
{
FieldInfo fld = t.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
return fld == null ? null : fld.GetValue(o);
}
protected void Page_Load(object sender, EventArgs e)
{
// first, get the ECB from the ISAPIWorkerRequest
var ctx = HttpContext.Current;
HttpWorkerRequest wr = (HttpWorkerRequest) GetField(typeof(HttpContext), ctx, "_wr");
IntPtr ecbPtr = IntPtr.Zero;
for (var t = wr.GetType(); t != null && t != typeof(object); t = t.BaseType)
{
object o = GetField(t, wr, "_ecb");
if (o != null)
{
ecbPtr = (IntPtr)o;
break;
}
}
// now call the ECB callback function to remove the item from cache
if (ecbPtr != IntPtr.Zero)
{
EXTENSION_CONTROL_BLOCK ecb = (EXTENSION_CONTROL_BLOCK)Marshal.PtrToStructure(
ecbPtr, typeof(EXTENSION_CONTROL_BLOCK));
uint dummy1, dummy2;
Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK invalidationCallback;
ecb.ServerSupportFunction(ecb.connID,
HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK,
out invalidationCallback,
out dummy1,
out dummy2);
bool success = invalidationCallback("/this/is/a/test");
}
}
}
From the discussion link you provided, it appears that the cache method or property exists but is protected or private so you can't access it.
Normally, you should stay away from using methods that are not part of the public API, but if you want to access them, use Reflection. With reflection, you can call private methods and get or set private properties and fields.
Related
I have a CoreWCF project with Autofac DI and I want to create some Integration Tests using a custom WebApplicationfactory.
I have used the custom WebApplicationFactory code for Autofac suggested here
https://github.com/autofac/Autofac/issues/1207 by Alistair Evans
This works fine for WebApi endpoints but it does not work for my CoreWCF endpoints.
What I found was that the Microsoft.AspNetCore.TestHost.TestServer that is created does not define the Features property with any IServerAddressesFeature. This causes a problem when the CoreWCF.ServiceObjectModel ApplyConfiguration is executed.
I have got around this by using reflection to set the Features property in the CreateServiceProvider method of the CustomServiceProviderFactory as shown below but does anybody know if there is a better way around this.
/// Enables in-memory integration testing for CoreWCF (outside-in testing via <see cref="HttpClient"/>).
///
/// Use these tests to exercise the entire HTTP stack, rather than create in-process ServiceModel channels.
///
/// <see href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1"/>
/// <seealso href="https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1"/>
/// </summary>
/// <typeparam name="TStartup"></typeparam>
public class WcfWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private ILifetimeScope _container;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestContainer<ContainerBuilder>(container =>
{
container.RegisterBuildCallback(BuildCallback);
});
builder.ConfigureTestServices(services =>
{
});
}
private void BuildCallback(ILifetimeScope obj)
{
_container = obj;
}
protected override IHost CreateHost(IHostBuilder builder)
{
builder.UseServiceProviderFactory(new CustomServiceProviderFactory(ClientOptions.BaseAddress.ToString()));
return base.CreateHost(builder);
}
protected override IHostBuilder? CreateHostBuilder()
{
return base.CreateHostBuilder();
}
}
/// <summary>
/// Based upon https://github.com/dotnet/aspnetcore/issues/14907#issuecomment-620750841 - only necessary because of an issue in ASP.NET Core
/// </summary>
public class CustomServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
{
private readonly string _testServerBaseAddress;
private AutofacServiceProviderFactory _wrapped;
private IServiceCollection _services;
public CustomServiceProviderFactory(string testServerBaseAddress)
{
_testServerBaseAddress = testServerBaseAddress;
_wrapped = new AutofacServiceProviderFactory();
}
public ContainerBuilder CreateBuilder(IServiceCollection services)
{
// Store the services for later.
_services = services;
return _wrapped.CreateBuilder(services);
}
public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
{
var sp = _services.BuildServiceProvider();
#pragma warning disable CS0612 // Type or member is obsolete
var filters = sp.GetRequiredService<IEnumerable<IStartupConfigureContainerFilter<ContainerBuilder>>>();
#pragma warning restore CS0612 // Type or member is obsolete
foreach (var filter in filters)
{
filter.ConfigureContainer(b => { })(containerBuilder);
}
var serviceProvider = _wrapped.CreateServiceProvider(containerBuilder);
// The CoreWCF.ServiceObjectModel ApplyConfiguration expects there to be at least one IServerAddressesFeature
// This is normally present for the Kestrel webserver but for the TestServer in the Microsoft.AspNetCore.TestHost namespace does not seem to populate it
// Had to use Refelction to set this since the Set method causes a containerBuilder Build operation
var addresses = new ServerAddressesFeature();
addresses.Addresses.Add(_testServerBaseAddress);
var features = new FeatureCollection();
features.Set<IServerAddressesFeature>(addresses);
var testServer = serviceProvider.GetRequiredService<IServer>();
var field = typeof(TestServer).GetField("<Features>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(testServer, features);
return serviceProvider;
}
}
}
I have found some clue in https://stackoverflow.com/a/48547597/3299257
Specifically in the announcement
Hosting no longer adds default server address when no address is explicitly configured
The WebHost will no longer add the default server address of http://localhost:5000 to the IServerAddressesFeature when none is specified. The configuration of the default server address will now be a responsibility of the server.
Addresses specified in IServerAddressesFeature are intended to be used by servers as a fallback when no explicit address is specified directly.
Adding feature was discussed here https://github.com/dotnet/aspnetcore/issues/5931 and came to conclusion that it won't be implemented in TestServer.
In my test scenario I do not use full WebApplicationFactory, but rather directly utilize TestServer so I could make a simpler soultion:
var addresses = new ServerAddressesFeature();
addresses.Addresses.Add("http://localhost");
var features = new FeatureCollection();
features.Set<IServerAddressesFeature>(addresses);
return new TestServer(builder, features);
In your scenario:
(sorry, this is not verified), you could use WebApplicationFactory.WithWebHostBuilder and then, invoke .UseTestServer constructing your own TestServer and providing feature collection in the second argument.
factory.WithWebHostBuilder(builder => {
var addresses = new ServerAddressesFeature();
addresses.Addresses.Add("http://localhost");
var features = new FeatureCollection();
features.Set<IServerAddressesFeature>(addresses);
builder.UseTestServer(new TestServer(builder, features));
});
Here are the details of my Development Environment:
Visual Studio 2012 Ultimate with Update 4
Google Chrome Version 38.0.2125.111 m
Windows 7 Professional with 32-bit Operating System
Coded UITest Builder 11.0.60315.1
Our software team is creating an ASP.NET web application, and the customer has requested that we use Microsoft Visual Studio 2012 with Microsoft CodedUI to run automated tests.
I ran our ASP.NET application in Google Chrome Version 38.0.2125.111 m
I took steps to use the Microsoft CodedUI to record some Browser-user interaction steps on our ASP.NET application which ultimately led to generating some code.
The following is the generated code based on the recorded Brower-user interaction that I conducted on our ASP.NET Web Application
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by coded UI test builder.
// Version: 11.0.0.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
// ------------------------------------------------------------------------------
namespace JigsawEMISTCodedUITestProject
{
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Input;
using Microsoft.VisualStudio.TestTools.UITest.Extension;
using Microsoft.VisualStudio.TestTools.UITesting;
using Microsoft.VisualStudio.TestTools.UITesting.WinControls;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Keyboard = Microsoft.VisualStudio.TestTools.UITesting.Keyboard;
using Mouse = Microsoft.VisualStudio.TestTools.UITesting.Mouse;
using MouseButtons = System.Windows.Forms.MouseButtons;
using Microsoft.VisualStudio.TestTools.UITesting.HtmlControls;
[GeneratedCode("Coded UITest Builder", "11.0.60315.1")]
public partial class UIMap
{
/// <summary>
/// RecordedMethod1
/// </summary>
public void RecordedMethod1()
{
#region Variable Declarations
WinClient uIChromeLegacyWindowClient = this.UIJigsawBetaEnviromentWindow.UIChromeLegacyWindowWindow.UIChromeLegacyWindowClient;
#endregion
// Click 'Chrome Legacy Window' client
Mouse.Click(uIChromeLegacyWindowClient, new Point(151, 25));
// Click 'Chrome Legacy Window' client
Mouse.Click(uIChromeLegacyWindowClient, new Point(150, 35));
// Click 'Chrome Legacy Window' client
Mouse.Click(uIChromeLegacyWindowClient, new Point(709, 90));
}
#region Properties
public UIJigsawBetaEnviromentWindow UIJigsawBetaEnviromentWindow
{
get
{
if ((this.mUIJigsawBetaEnviromentWindow == null))
{
this.mUIJigsawBetaEnviromentWindow = new UIJigsawBetaEnviromentWindow();
}
return this.mUIJigsawBetaEnviromentWindow;
}
}
#endregion
#region Fields
private UIJigsawBetaEnviromentWindow mUIJigsawBetaEnviromentWindow;
#endregion
}
[GeneratedCode("Coded UITest Builder", "11.0.60315.1")]
public class UIJigsawBetaEnviromentWindow : WinWindow
{
public UIJigsawBetaEnviromentWindow()
{
#region Search Criteria
this.SearchProperties[WinWindow.PropertyNames.Name] = "Jigsaw [Beta Enviroment] - Google Chrome";
// this.SearchProperties[WinWindow.PropertyNames.Name] = "Jigsaw";
this.SearchProperties[WinWindow.PropertyNames.ClassName] = "Chrome_WidgetWin_1";
// this.SearchProperties[WinWindow.PropertyNames.ClassName] = "Chrome";
this.WindowTitles.Add("Jigsaw [Beta Enviroment] - Google Chrome");
// this.WindowTitles.Add("Jigsaw");
#endregion
}
#region Properties
public UIChromeLegacyWindowWindow UIChromeLegacyWindowWindow
{
get
{
if ((this.mUIChromeLegacyWindowWindow == null))
{
this.mUIChromeLegacyWindowWindow = new UIChromeLegacyWindowWindow(this);
}
return this.mUIChromeLegacyWindowWindow;
}
}
#endregion
#region Fields
private UIChromeLegacyWindowWindow mUIChromeLegacyWindowWindow;
#endregion
}
[GeneratedCode("Coded UITest Builder", "11.0.60315.1")]
public class UIChromeLegacyWindowWindow : WinWindow
{
public UIChromeLegacyWindowWindow(UITestControl searchLimitContainer) :
base(searchLimitContainer)
{
#region Search Criteria
this.SearchProperties[WinWindow.PropertyNames.ControlId] = "144212160";
this.WindowTitles.Add("Jigsaw [Beta Enviroment] - Google Chrome");
// this.WindowTitles.Add("Jigsaw");
#endregion
}
#region Properties
public WinClient UIChromeLegacyWindowClient
{
get
{
if ((this.mUIChromeLegacyWindowClient == null))
{
this.mUIChromeLegacyWindowClient = new WinClient(this);
#region Search Criteria
this.mUIChromeLegacyWindowClient.SearchProperties[WinControl.PropertyNames.Name] = "Chrome Legacy Window";
// this.mUIChromeLegacyWindowClient.SearchProperties[WinControl.PropertyNames.Name] = "Chrome";
this.mUIChromeLegacyWindowClient.WindowTitles.Add("Jigsaw [Beta Enviroment] - Google Chrome");
// this.mUIChromeLegacyWindowClient.WindowTitles.Add("Jigsaw");
#endregion
}
return this.mUIChromeLegacyWindowClient;
}
}
#endregion
#region Fields
private WinClient mUIChromeLegacyWindowClient;
#endregion
}
}
Microsoft CodedUI is throwing Error because it fails to see 'Chrome Legacy Window'
I ran the code, but I get the following Error:
Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotFoundException was unhandled by user code
HResult=-268111872
Message=The playback failed to find the control with the given search properties. Additional Details:
TechnologyName: 'MSAA'
ControlType: 'Client'
Name: 'Chrome Legacy Window'
Source=Microsoft.VisualStudio.TestTools.UITesting
BasicMessage=The playback failed to find the control with the given search properties.
RootElement=""
StackTrace:
at Microsoft.VisualStudio.TestTools.UITesting.Playback.MapControlNotFoundException (COMException ex, IPlaybackContext context)
at Microsoft.VisualStudio.TestTools.UITesting.Playback.MapAndThrowComException (COMException innerException, IPlaybackContext context)
at Microsoft.VisualStudio.TestTools.UITesting.Playback.MapAndThrowException (SystemException exception, IPlaybackContext context)
at Microsoft.VisualStudio.TestTools.UITesting.Playback.MapAndThrowException (SystemException exception, String queryId)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.FindFirstDescendant (String queryId, Int32 maxDepth, Int32& timeLeft)
at Microsoft.VisualStudio.TestTools.UITesting.SearchHelper.GetElement(Boolean useCache, ISearchArgument searchArg)
at Microsoft.VisualStudio.TestTools.UITesting.SearchHelper.Search (ISearchArgument searchArg)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.FindInternal()
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.FindPrivate()
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.<Find>b__d()
at Microsoft.VisualStudio.TestTools.UITesting.CodedUITestMethodInvoker.InvokeMethod[T](Func`1 function, UITestControl control, Boolean firePlaybackErrorEvent, Boolean logAsAction)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.Find()
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.GetPropertyPrivate (String propertyName)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.<>c__DisplayClass11.<GetProperty>b __10()
at Microsoft.VisualStudio.TestTools.UITesting.CodedUITestMethodInvoker.InvokeMethod[T](Func`1 function, UITestControl control, Boolean firePlaybackErrorEvent, Boolean logAsAction)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.GetProperty(String propertyName)
at Microsoft.VisualStudio.TestTools.UITesting.ALUtility.GetTechElementFromUITestControl (UITestControl uiTestControl)
at Microsoft.VisualStudio.TestTools.UITesting.ActionExecutorManager.GetActionExecutor (UITestControl uiControl)
at Microsoft.VisualStudio.TestTools.UITesting.Mouse.ClickImplementation (UITestControl control, MouseButtons button, ModifierKeys modifierKeys, Point relativeCoordinate)
at Microsoft.VisualStudio.TestTools.UITesting.Mouse.<>c__DisplayClass6.<Click>b__5()
at Microsoft.VisualStudio.TestTools.UITesting.CodedUITestMethodInvoker.InvokeMethod[T](Func`1 function, UITestControl control, Boolean firePlaybackErrorEvent, Boolean logAsAction)
at Microsoft.VisualStudio.TestTools.UITesting.Mouse.Click(UITestControl control, MouseButtons button, ModifierKeys modifierKeys, Point relativeCoordinate)
at Microsoft.VisualStudio.TestTools.UITesting.Mouse.Click(UITestControl control, Point relativeCoordinate)
at JigsawEMISTCodedUITestProject.UIMap.RecordedMethod1() in d:\EMIS\JigsawEMISTCodedUITestProject\JigsawEMISTCodedUITestProject\UIMap.cs:line 42
at JigsawEMISTCodedUITestProject.CodedUITest1.CodedUITestMethod1() in d:\EMIS\JigsawEMISTCodedUITestProject\JigsawEMISTCodedUITestProject\CodedUITest1.cs:line 30
InnerException: System.Runtime.InteropServices.COMException
HResult=-2147467259
Message=Error HRESULT E_FAIL has been returned from a call to a COM component.
Source=Microsoft.VisualStudio.TestTools.UITest.Playback
ErrorCode=-2147467259
StackTrace:
at Microsoft.VisualStudio.TestTools.UITest.Playback.Engine.IScreenElement.FindAllDescendants (String bstrQueryId, Object& pvarResKeys, Int32 cResKeys, Int32 nMaxDepth)
at Microsoft.VisualStudio.TestTools.UITest.Playback.ScreenElement.FindAllScreenElement(String queryId, Int32 depth, Boolean singleQueryId, Boolean throwException, Boolean resetSkipStep)
at Microsoft.VisualStudio.TestTools.UITest.Playback.ScreenElement.FindScreenElement(String queryId, Int32 depth, Boolean resetSkipStep)
at Microsoft.VisualStudio.TestTools.UITest.Playback.ScreenElement.FindScreenElement(String queryId, Int32 depth)
at Microsoft.VisualStudio.TestTools.UITesting.UITestControl.FindFirstDescendant(String queryId, Int32 maxDepth, Int32& timeLeft)
InnerException:
Do I need to install some drivers for popular browsers ( i.e Google Chrome, Mozilla Firefox, IE, etc.) ?
I restarted my development computer a couple times, but still have same problem.
Please help me.
There's actually a Selenium framework that was ported to run with Coded UI by a developer at Microsoft, which you can find here, that will use the Selenium components to run the tests in Chrome or Firefox. IE is handled by the Coded UI itself, so if your BrowserWindow.BrowserType is "IE", then it'll just run the vanilla Coded UI that's shipped with your Visual Studio.
The Q&A section of that page is where you'll find the most support for questions about the tool itself, but if you have trouble getting started, this blog was very helpful.
To avoid web service's problem of not being able to pass complex objects like dictionaries and trees, I created a small struct inside the class with a few values fields. However, the web service is in a seperate project in the solution and I'm unsure how the behind code that calls the webService function would know what the struct is. Should I copy the struct to the behind code file? Can I import it?
Here's a small example:
namespace mYWebService{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class Service1 : System.Web.Services.WebService
{
struct TreeData
{
private readonly string text;
private readonly string parent;
private string val;
public TreeData (string Text, string Parent)
{
this.text = Text;
this.parent = Parent;
this.val = "";
}
public TreeData (string Text, string Parent, string Value)
{
this.text = Text;
this.parent = Parent;
this.val = Value;
}
public string Text { get { return text; } }
public string Parent { get { return parent; } }
public string Value { get { return val; } }
}
[WebMethod]`
public TreeData getTree(){
TreeData myTree = new TreeData("1","2","3");
return myTree;
}}
When you generate the binding in the client code, all necessary complex data types will get created automatically, because they are described in the service's metadata. However, you should rather use WCF these days providing there's no hard requirement to use the old-fashioned .NET 2.0 web services (i.e. the WebService class).
You will have a hard time compiling this because you are exposing a private struct in a public method. In the very least, the struct must be made public. I also recommend that you put your struct outside of the class, since inner classes / structs /etc is bad practice (this is my personal opinion, however you do not see them used much in e.g. the .net framework, indicating that Microsoft doesn't like them much either).
Keep in mind that web services are distributed by nature, thus you should not have to rely on references to the class directly. This is a SOAP service (I think), and the framework will expose the metadata of the service. This metadata can be used by Visual Studio to auto generate a proxy client which can be used to call the service.
Here's a simple way to set up a proxy:
Start the web service project executable (not in debug mode, you will still need to be able to use Visual Studio for the next steps)
Select the project where your web service client (the code that calls the service) is located and add a service reference
This will open a dialog where you can enter the service endpoint (url). Enter the endpoint where the service is running, and you should be able to select in this dialog
Once the reference is added, some autogenerated proxy code should be generated for you. This will give you access to your Method.
Finally I do agree with Ondrej Tucny that you should look into WCF instead
I have an existing multi-tenant ASP.NET application where all users authenticate against a single SQL Server database. This database also contains several other settings type data that is used within the application. Each client after authentication, utilizes their own SQL Server database for data storage, for isolation purposes. Essentially all of the client database are identical and reside on the same server, but reside on one or more servers as well.
The application is currently written in asp.net 2.5 framework and utilizes the Microsoft Practices Enterprise Library for DAL, and we are looking to migrate to 4.0 and implement NHibernate to replace the MPEL.
I have implemented a solution already using NHibernate and the 4.0 framework, so I am familiar with the concepts. I found the resources for my current session manager here as a matter of fact. But that application only had a single database, so not much too it. The implementation is essentially what you see here:
http://www.lostechies.com/blogs/nelson_montalvo/archive/2007/03/30/simple-nhibernate-example-part-4-session-management.aspx
The other solutions that I have seen suggest multiple config entries and/or files to manage this, but that is not desirable, since we may add new clients frequently and all of the connection information is already maintained in the authentication database.
Does anyone have any suggestions on how I might be able to pass in the client's connection string to a session manager?
The following is my current session manager class, which is based on the article mentioned above.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Web;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Cache;
using singlepoint.timeclock.domain;
namespace singlepoint.timeclock.repositories
{
/// <summary>
/// Handles creation and management of sessions and transactions. It is a singleton because
/// building the initial session factory is very expensive. Inspiration for this class came
/// from Chapter 8 of Hibernate in Action by Bauer and King. Although it is a sealed singleton
/// you can use TypeMock (http://www.typemock.com) for more flexible testing.
/// </summary>
public sealed class nHibernateSessionManager
{
private ISessionFactory idadSessionFactory;
private ISessionFactory clientSessionFactory;
private string _client;
#region Thread-safe, lazy Singleton
// lazy initialisation, therefore initialised to null
private static nHibernateSessionManager instance = null;
/// <summary>
/// This is a thread-safe, lazy singleton. See http://www.yoda.arachsys.com/csharp/singleton.html
/// for more details about its implementation.
/// </summary>
public static nHibernateSessionManager Instance
{
get { return GetInstance(); }
}
public static nHibernateSessionManager GetInstance()
{
// lazy init.
if (instance == null)
instance = new nHibernateSessionManager();
return instance;
} // GetInstance
/// <summary>
/// Initializes the NHibernate session factory upon instantiation.
/// </summary>
private nHibernateSessionManager()
{
InitSessionFactory();
}
/// <summary>
/// Initializes the NHibernate session factory upon instantiation.
/// </summary>
private nHibernateSessionManager(string client)
{
InitSessionFactory();
InitClientSessionFactory(client);
}
/// <summary>
/// Assists with ensuring thread-safe, lazy singleton
/// </summary>
private class Nested
{
static Nested()
{
}
internal static readonly nHibernateSessionManager nHibernatenHibernateSessionManager = new nHibernateSessionManager();
}
#endregion
private void InitSessionFactory()
{
var configuration = new Configuration();
configuration.Configure(System.Configuration.ConfigurationManager.AppSettings["IDAD_HBM"]);
configuration.AddAssembly(typeof(enterprise).Assembly);
idadSessionFactory = configuration.BuildSessionFactory();
}
private void InitClientSessionFactory(string client)
{
var configuration = new Configuration();
configuration.Configure(System.Configuration.ConfigurationManager.AppSettings["Client_IDAD_HBM"]);
configuration.SetProperty("connection.connection_string", client);
configuration.AddAssembly(typeof(enterprise).Assembly);
clientSessionFactory = configuration.BuildSessionFactory();
}
/// <summary>
/// Allows you to register an interceptor on a new session. This may not be called if there is already
/// an open session attached to the HttpContext. If you have an interceptor to be used, modify
/// the HttpModule to call this before calling BeginTransaction().
/// </summary>
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = ThreadSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a session has already been opened");
}
GetSession(interceptor);
}
public ISession GetSession()
{
return GetSession(null);
}
/// <summary>
/// Gets a session with or without an interceptor. This method is not called directly; instead,
/// it gets invoked from other public methods.
/// </summary>
private ISession GetSession(IInterceptor interceptor)
{
ISession session = ThreadSession;
if (session == null)
{
if (interceptor != null)
{
session = idadSessionFactory.OpenSession(interceptor);
}
else
{
session = idadSessionFactory.OpenSession();
}
ThreadSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = ThreadSession;
ThreadSession = null;
if (session != null && session.IsOpen)
{
session.Close();
}
}
public void BeginTransaction()
{
ITransaction transaction = ThreadTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
ThreadTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = ThreadTransaction;
try
{
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Commit();
ThreadTransaction = null;
}
}
catch (HibernateException ex)
{
RollbackTransaction();
throw ex;
}
}
public void RollbackTransaction()
{
ITransaction transaction = ThreadTransaction;
try
{
ThreadTransaction = null;
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Rollback();
}
}
catch (HibernateException ex)
{
throw ex;
}
finally
{
CloseSession();
}
}
/// <summary>
/// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms
/// specific <see cref="CallContext" />. Discussion concerning this found at
/// http://forum.springframework.net/showthread.php?t=572.
/// </summary>
private ITransaction ThreadTransaction
{
get
{
if (IsInWebContext())
{
return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
}
else
{
return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
}
}
set
{
if (IsInWebContext())
{
HttpContext.Current.Items[TRANSACTION_KEY] = value;
}
else
{
CallContext.SetData(TRANSACTION_KEY, value);
}
}
}
/// <summary>
/// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms
/// specific <see cref="CallContext" />. Discussion concerning this found at
/// http://forum.springframework.net/showthread.php?t=572.
/// </summary>
private ISession ThreadSession
{
get
{
if (IsInWebContext())
{
return (ISession)HttpContext.Current.Items[SESSION_KEY];
}
else
{
return (ISession)CallContext.GetData(SESSION_KEY);
}
}
set
{
if (IsInWebContext())
{
HttpContext.Current.Items[SESSION_KEY] = value;
}
else
{
CallContext.SetData(SESSION_KEY, value);
}
}
}
private static bool IsInWebContext()
{
return HttpContext.Current != null;
}
private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
private const string SESSION_KEY = "CONTEXT_SESSION";
[Obsolete("only until we can fix the session issue globally")]
internal ISession OpenSession()
{
return idadSessionFactory.OpenSession();
}
}
}
This is being called from a repository class like so:
public string getByName(string name)
{
return getByName(nHibernateSessionManager.Instance.GetSession(), name);
}
What I would really like to be able to do is the following:
public string getByName(string name, string clientConnectionString)
{
return getByName(nHibernateSessionManager.Instance.GetSession(clientConnectionString), name);
}
But I am having trouble modifying my existing session manager to accomodate this.
You appear to be asking to swap a connection for a given session. Or rather that is certainly what the code you have written is asking - "return a session identified by the name parameter, and it should also now use the connection string provided by this method."
That is not possible. NHibernate builds a session (and actually really a session factory) per connection and once built the factory and session are immutable. You cannot change connections for an existing session.
I got the impression that your application involves mostly in initial connection string that is the moving target, but after that your "real" session is on a single database. If that is the case, NHibernate can easily do this. If that is not the case, well, some things NHibernate is just not that well suited for. Maybe understanding a little more about the basis NHibernate operates on is helpful either way?
One of my genuine criticisms of NHibernate is that you have a somewhat arcane use of terminology and the well known unhelpful nature of it's exception messages. These coupled with the fact that what it is doing is in reality mechanically complicated tends to really obscure that there is a relatively simple and technically sound underlying model.
In this case, if you think about it this business of an immutable session makes a lot of sense. NHibernate connects to a database, but it also maintains objects in the session so they can be persisted back to that database at a later time. NHibernate does not support changing connections per session because there may already be other objects in the session and if you change connections their persistence is no longer assured.
Now, what you can do is create a factory/session per database for multiple databases and access them in one application, but objects still belong to their own session. You can even move objects to a new session with a different connection. In this case you have what would logically be a "replication" scenario. NHibernate supports this but you have to do most of the work. This also makes sense - they really cannot give you that as stable out of the box functionality, you have to manage a process like that on your own.
You can also build code to do exactly what you are asking. But think about what that is. Make a session, not per database, but only for this specific instance of this specific repository. I am thinking that is most likely not really what you want. But that is exactly what the semantics of your request are saying to do. Your existing class, On the other hand, was built on different semantics which are more typically what people want - "Build a session for this particular connection definition, i.e this database."
A real need to inject a connection string at the repository level implies that now not only is the database a moving target, but at the actual table level the target also moves. If that is the case, NHibernate is possibly not a good option. If that is not the case, you may be trying to mix programming paradigms. NHiberate imposes a few what I would call "assumptions" rather than any sort of real "limitations" and in return you don't have to write a bunch of code that would allow you a finer grain of control because often you really don't need that additional control.
Sorry if this is no longer a direct answer to your question, hopefully it is helpful somehow.
Original Answer:
Sure, since the connection info is in the authentication database this is easy.
1) Configure NHibernate in the "usual" fashion and point the config at the authentication database. Get the db connection for the user, and then close that session and session factory. You are done with that one now.
2) Create a new session etc this time in code instead of a config file.
class MyNewSession
{
private ISession _session;
private ISessionFactory _factory;
public void InitializeSession()
{
NHibernate.Cfg.Configuration config = new NHibernate.Cfg.Configuration();
config.Properties.Clear();
IDictionary props = new Hashtable();
// Configure properties as needed, this is pretty minimal standard config here.
// Can read in properties from your own xml file or whatever.
// Just shown hardcoded here.
props["proxyfactory.factory_class"] = "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle";
props["connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
props["dialect"] = "NHibernate.Dialect.MsSql2000Dialect";
props["connection.driver_class"] = "NHibernate.Driver.SqlClientDriver";
props["connection.connection_string"] = "<YOUR CONNECTION STRING HERE>";
props["connection.isolation"] = "ReadCommitted";
foreach (DictionaryEntry de in props)
{
config.Properties.Add(de.Key.ToString(), de.Value.ToString());
}
// Everything from here on out is the standard NHibernate calls
// you already use.
// Load mappings etc, etc
// . . .
_factory = config.BuildSessionFactory();
_session = _factory.OpenSession();
}
}
I know this is old but if you have not found a solution I hope this will help,
I created a solution that uses multisessionfactory using unhaddins (I made alterations to suit my needs).
Basically the multisession factory creates session factories for each database and stores in Application object.
Depending on the client the call to getfactory("name of factory from factory config file") returns correct database to query on.
You will have to alter your management module to support this and all of your repositories to support the change in management. This may be impractical at first but you have to alter them anyway. Your calls from your repository can be something like this:
public string getByName(string name)
{
return getByName(nHibernateSessionManager.SessionFactoryManager.GetFactory(Session["session variable that holds client session factory name that was set on login"]).GetCurrentSession(), name);
}
or (creating a method in the sessionmanager to return session of a given factory) your code could be like this
public string getByName(string name)
{
return getByName(nHibernateSessionManager.GetSession(Session["session variable that holds client session factory name that was set on login"]), name);
}
I'm using the [System.Web.Script.Services.ScriptService] tag to use web services callable from client side javascript. What I need is a way of globally logging any unhandled exceptions in those methods. On the client side, I get the error callback and can proceed from there, but I need a server-side catch to log the exception.
The guy at this url:
http://ayende.com/Blog/archive/2008/01/06/ASP.Net-Ajax-Error-Handling-and-WTF.aspx
suggests that this can't be done.
Is that accurate? Do I seriously have to go to every single webmethod in the entire system and try/catch the method as a whole.
You can use an HTTP module to capture the exception message, stack trace and exception type that is thrown by the web service method.
First some background...
If a web service method throws an exception the HTTP response has a status code of 500.
If custom errors are off then the web
service will return the exception
message and stack trace to the client
as JSON. For example:{"Message":"Exception
message","StackTrace":" at
WebApplication.HelloService.HelloWorld()
in C:\Projects\Stackoverflow
Examples\WebApplication\WebApplication\HelloService.asmx.cs:line
22","ExceptionType":"System.ApplicationException"}
When custom errors are on then the
web service returns a default message
to the client and removes the stack
trace and exception type:{"Message":"There was an error processing the request.","StackTrace":"","ExceptionType":""}
So what we need to do is set custom errors off for the web service and plug in an HTTP module that:
Checks if the request is for a web service method
Checks if an exception was thrown - that is, a status code of 500 is being returned
If 1) and 2) are true then get the original JSON that would be sent to the client and replace it with the default JSON
The code below is an example of an HTTP module that does this:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
public class ErrorHandlerModule : IHttpModule {
public void Init(HttpApplication context) {
context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
context.EndRequest += OnEndRequest;
}
static void OnPostRequestHandlerExecute(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
// TODO: Update with the correct check for your application
if (context.Request.Path.StartsWith("/HelloService.asmx")
&& context.Response.StatusCode == 500) {
context.Response.Filter =
new ErrorHandlerFilter(context.Response.Filter);
context.EndRequest += OnEndRequest;
}
}
static void OnEndRequest(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
ErrorHandlerFilter errorHandlerFilter =
context.Response.Filter as ErrorHandlerFilter;
if (errorHandlerFilter == null) {
return;
}
string originalContent =
Encoding.UTF8.GetString(
errorHandlerFilter.OriginalBytesWritten.ToArray());
// If customErrors are Off then originalContent will contain JSON with
// the original exception message, stack trace and exception type.
// TODO: log the exception
}
public void Dispose() { }
}
This module uses the following filter to override the content sent to the client and to store the original bytes (which contain the exception message, stack trace and exception type):
public class ErrorHandlerFilter : Stream {
private readonly Stream _responseFilter;
public List OriginalBytesWritten { get; private set; }
private const string Content =
"{\"Message\":\"There was an error processing the request.\"" +
",\"StackTrace\":\"\",\"ExceptionType\":\"\"}";
public ErrorHandlerFilter(Stream responseFilter) {
_responseFilter = responseFilter;
OriginalBytesWritten = new List();
}
public override void Flush() {
byte[] bytes = Encoding.UTF8.GetBytes(Content);
_responseFilter.Write(bytes, 0, bytes.Length);
_responseFilter.Flush();
}
public override long Seek(long offset, SeekOrigin origin) {
return _responseFilter.Seek(offset, origin);
}
public override void SetLength(long value) {
_responseFilter.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count) {
return _responseFilter.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count) {
for (int i = offset; i < offset + count; i++) {
OriginalBytesWritten.Add(buffer[i]);
}
}
public override bool CanRead {
get { return _responseFilter.CanRead; }
}
public override bool CanSeek {
get { return _responseFilter.CanSeek; }
}
public override bool CanWrite {
get { return _responseFilter.CanWrite; }
}
public override long Length {
get { return _responseFilter.Length; }
}
public override long Position {
get { return _responseFilter.Position; }
set { _responseFilter.Position = value; }
}
}
This method requires custom errors to be switched off for the web services. You would probably want to keep custom errors on for the rest of the application so the web services should be placed in a sub directory. Custom errors can be switched off in that directory only using a web.config that overrides the parent setting.
You run the Stored Procedure in the backend. Then, for a single variable, it returns more than 1 value. Because of that, a conflicts occurs, and, this error is thrown.
I know this doesn't answer the question per-say, but I went on my own quest a while back to find this out and would up empty handed. Ended up wrapping each web service call in a try/catch, and the catch calls our error logger. Sucks, but it works.
In ASP.Net it is possible to catch all run handled exceptions using a global error handler although the blog post suggest this would not work but you could experiment with this approach trying to rethrow the error in some way?
Another idea would be to look at the open source elmah (Error Logging Modules and Handlers) for ASP.Net that might help or someone in that community may have an idea.