ASP.NET Core: Custom IFileProvider prevents default IFileProvider from working - c#

I am trying to serve some JavaScript from embedded resources in a class library. I managed to find out about the IFileProvider and created my own which is now working well. However, the problem I have now is that physical static files (from wwwroot) are no longer found.
I have the following in my Startup.cs file:
app.UseStaticFiles(
new StaticFileOptions()
{
// Override file provider to allow embedded resources
FileProvider = new CompositeFileProvider(
HostingEnvironment.ContentRootFileProvider,
new EmbeddedScriptFileProvider()),
//etc
});
I would have assumed using the CompositeFileProvider would mean that if the file is not found in one of the file providers, then it will try the other. I am also assuming that the default file provider is the one I specified as HostingEnvironment.ContentRootFileProvider. Is this incorrect?
The only other thing I can think of is that the problem is coming from inside my provider itself in the GetFileInfo() method. The definition of which is as follows:
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
return new NotFoundFileInfo(subpath);
}
if (subpath.StartsWith("/", StringComparison.Ordinal))
{
subpath = subpath.Substring(1);
}
var metadata = EmbeddedScripts.FindEmbeddedResource(subpath);
if (metadata == null)
{
return new NotFoundFileInfo(subpath);
}
return new EmbeddedResourceFileInfo(metadata);
}
Could it be that returning NotFoundFileInfo(subpath) is causing my problems for physical css, js and other static files? If so, what should I be returning here instead so that the system knows to use the other file provider?

OK after a little digging in the source code (isn't it great that .NET is now open source?!), I managed to find the following links were very helpful indeed:
CompositeFileProvider.cs
- Based on the implementation in GetFileInfo(), I can see that I should pass back null instead of NotFoundFileInfo(subpath) if I want the other providers to try resolve it.
StaticFileMiddleware.cs
- This file shows that if the FileProvider is not specified (null) when setting up static file configuration with app.UseStaticFiles, then it will resolve one with the following line of code:
_fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
And looking at Helpers.cs shows the following code:
internal static IFileProvider ResolveFileProvider(IHostingEnvironment hostingEnv)
{
if (hostingEnv.WebRootFileProvider == null)
{
throw new InvalidOperationException("Missing FileProvider.");
}
return hostingEnv.WebRootFileProvider;
}
Therefore, my assumption of using HostingEnvironment.ContentRootFileProvider was incorrect. I should be using HostingEnvironment.WebRootFileProvider instead.
Everything now works as it should.

Related

How to create transform for .msi file using c#

I'm trying to create a transform for .msi file in C#. Here is my code:
public static void CreateTransforms(string original_MSI, string backup_MSI, string MSTpath, string query)
{
File.Copy(original_MSI, backup_MSI, true);
using (var origDatabase = new Microsoft.Deployment.WindowsInstaller.Database(original_MSI, DatabaseOpenMode.ReadOnly))
{
using (var database = new Microsoft.Deployment.WindowsInstaller.Database(backup_MSI, DatabaseOpenMode.Direct))
{
//database.Execute("Update `Property` Set `Property`.`Value` = 'Test' WHERE `Property`.`Property` = 'ProductName'");
database.Execute(query);
database.GenerateTransform(origDatabase, MSTpath);
database.CreateTransformSummaryInfo(origDatabase, MSTpath, TransformErrors.None, TransformValidations.None);
}
}
}
I got the following error : "This installation package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer package." in the step create transform summary info. I used "Microsoft.Deployment.WindowsInstaller.dll" library. Any help would be great.
A quick read of this static method looked correct so I created a console app out of it. It works fine for me on my machine. I would look at your calling method and make sure the data being passed is correct. I get really nervous any time I have a method that takes 4 strings as arguments as that leaves a lot to desire in terms of type safety.
when CreateTransforms start, it open database and it does not close it ...
you must commit and close the database before apply a new transform!
database.GenerateTransform(origDatabase, TRANSFORM);
database.CreateTransformSummaryInfo(origDatabase, TRANSFORM, TransformErrors.None, TransformValidations.None);
database.Commit();
database.Close();

Update Data Source and DataSet reference for SSRS 2012 Deployment from C#

EDIT: I think I can simplify this question a bit to ask for only what is needed to know:
I am working with C# using the SSRS 2010 Web Service:
'ReportService2010.asmx' http://technet.microsoft.com/en-us/library/ee640743.aspx
I can use the method 'CreateDataSource' to create a Datasource on an instance of an SSRS Server http:// (servername)/ReportServer.
I can also use the method 'CreateCatalogItem' to create a report on a server from referencing a project's RDL local file to serialize it to a byte array and then pass that as a 'Definition' to the method to create it on the server.
Now everything I do works with a caveat, and a major one. I can only deploy everything to the same folder. If I deploy a Data Source to say the 'Data Sources' folder and then a report to say: 'Test Reports', the report does not know it has a shared data source to reference at a different location. So I dug a little at the technet articles and have tried to 'GetItemDataSources' method but it only gives a name and a type for the ReportingService2010.DataSource return type. Does anyone know the method to link up a 'Report' or 'Dataset's CatalogItem property of 'DataSource', so it points to a reference in a different folder on the SSRS Server when deploying? There has to be a way to do it as I know I can deploy from Business Intelligence Development Studio and it can do this.
I've had similar issues when deploying report files; when deploying through rs.exe or code you run into these issues where reports lose their link to a Data Source.
We solved this by explicitly pointing the report to the server-side Data Source immediately after being deployed by our application; is this similar to what you're trying to do?
Anyway, here's the slightly adapted code we use in our report deployment application:
static void SetReportDataSource(string reportPath)
{
string dsPath = CombinePath(DataSourcePath, DataSourceFolder, DataSourceName);
DataSourceReference dsRef = new DataSourceReference()
{
Reference = dsPath
};
DataSource ds = new DataSource();
ds.Item = dsRef as DataSourceDefinitionOrReference;
ds.Name = DataSourceName;
var rptDataSources = Server.GetItemDataSources(reportPath);
foreach (var rptDs in rptDataSources)
{
Server.SetItemDataSources(filePath, new DataSource[] { ds });
}
}
So, basically we have variables that define information like the Data Source name, Data Source location on server, and the same for a report. They can be in different folders.
Based on this, we create a new reference to a Data Source and then repoint the report to this using SetItemDataSources.
This sorted out the Data Source issue for me, anyway. Not sure about Shared Datasets and how they handle all of this, but hopefully this will be of some help.
Also, just thought that this would be using the ReportService2005 endpoint, but it's probably not too different for ReportService2010.
Edit:
For the paths mentioned here, these are relative to the server, e.g. /Reports/. You don't need the fully qualified name as you define the Url property of the ReportService2010 object which contains the destination.
Maybe this might be some help. I used it to reset the DataSource for all the reports in a given Parent Folder, and it's subfolders:
using System;
using GetPropertiesSample.ReportService2010;
using System.Diagnostics;
using System.Collections.Generic; //<== required for LISTS
using System.Reflection;
namespace GetPropertiesSample
{
class Program
{
static void Main(string[] args)
{
GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource("0_Contacts"); //<=== This is the parent folder
}
private static void GetListOfObjectsInGivenFolder_and_ResetTheReportDataSource(string sParentFolder)
{
// Create a Web service proxy object and set credentials
ReportingService2010 rs = new ReportingService2010();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
CatalogItem[] reportList = rs.ListChildren(#"/" + sParentFolder, true);
int iCounter = 0;
foreach (CatalogItem item in reportList)
{
iCounter += 1;
Debug.Print(iCounter.ToString() + "]#########################################");
if (item.TypeName == "Report")
{
Debug.Print("Report: " + item.Name);
ResetTheDataSource_for_a_Report(item.Path, "/DataSources/Shared_New"); //<=== This is the DataSource that I want them to use
}
}
}
private static void ResetTheDataSource_for_a_Report(string sPathAndFileNameOfTheReport, string sPathAndFileNameForDataSource)
{
//from: http://stackoverflow.com/questions/13144604/ssrs-reportingservice2010-change-embedded-datasource-to-shared-datasource
ReportingService2010 rs = new ReportingService2010();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
string reportPathAndName = sPathAndFileNameOfTheReport;
//example of sPathAndFileNameOfTheReport "/0_Contacts/207_Practices_County_CareManager_Role_ContactInfo";
List<ReportService2010.ItemReference> itemRefs = new List<ReportService2010.ItemReference>();
ReportService2010.DataSource[] itemDataSources = rs.GetItemDataSources(reportPathAndName);
foreach (ReportService2010.DataSource itemDataSource in itemDataSources)
{
ReportService2010.ItemReference itemRef = new ReportService2010.ItemReference();
itemRef.Name = itemDataSource.Name;
//example of DataSource i.e. 'itemRef.Reference': "/DataSources/SharedDataSource_DB2_CRM";
itemRef.Reference = sPathAndFileNameForDataSource;
itemRefs.Add(itemRef);
}
rs.SetItemReferences(reportPathAndName, itemRefs.ToArray());
}
}
}

How to change an EF connection string, based on subdomain of MVC web site?

I have an EF5 ASP.NET MVC 3 (Razor) web site, running under IIS7. Now I want to be able to change the connection string to the MSSQL database depending on the subdomain of the URL, e.g. foo.mydomain.com should connect to my "Foo" database, and bar.mydomain.com should connect to the "Bar" database.
Obviously the DNS records are set up so that they all point to the same web site.
What's the most efficient way of achieving this?
why don't you start passing your own SqlConnection to your YourDbContext?
var partialConString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;
var connection = new SqlConnection("Initial Catalog=" + Request.Url.Host + ";" + partialConString);
var context = new MyDbContext(connection, true);
You can also change database in the DBContext:
context.Database.Connection.ChangeDatabase("newDbname");
It's not very easy...
You should change the constructor of object context to dynamically change the connection string.
Take the subdomain name using System.Web.HttpContext.Current.Request.Url.Host. Then use it to compute the proper connection string.
You should do this in the designer generated code. Of course this is not a good place.. to make it work use the T4 templating. Open your model and right click on the blank designer surface, then select "Add code generation item" -> Ado.net entity object generation. This will create a .tt file. Open it and look for the constructor syntax. Add your logic there.
Good luck!
I've come up with what I feel is a better solution than all those proposed to date. I'm using the default EntityModelCodeGenerator, so perhaps there are other, better, solutions for other templates - but this works for me:
Create the other half of the partial class MyEntities.
Override OnContextCreated(), which is called from within the class constructor.
Change the store connection string using a regex.
This comes out as follows:
partial void OnContextCreated()
{
// change connection string, depending on subdomain
if (HttpContext.Current == null) return;
var host = HttpContext.Current.Request.Url.Host;
var subdomain = host.Split('.')[0];
switch (subdomain)
{
case "foo":
ChangeDB("Foo");
break;
case "bar":
ChangeDB("Bar");
break;
}
}
private void ChangeDB(string dbName)
{
var ec = Connection as EntityConnection;
if (ec == null) return;
var match = Regex.Match(ec.StoreConnection.ConnectionString, #"Initial Catalog\s*=.*?;", RegexOptions.IgnoreCase);
if (!match.Success) return;
var newDbString = "initial catalog={0};".Fmt(dbName);
ec.StoreConnection.ConnectionString = ec.StoreConnection.ConnectionString.Replace(match.Value, newDbString);
}
Either use different connection strings in the web.config. Maybe research a bit if you can have conditional XSL transformations, that way, when you publish on a specific configuration the web.Release.config will change your Web.Config to be what you need it to be.
Or, use |DataDirectory| string substitution - http://msdn.microsoft.com/en-us/library/cc716756.aspx
more on DataDirectory string substitution here:
http://forums.asp.net/t/1835930.aspx/1?Problem+With+Database+Connection
I guess, if you want to be by the book, create build configurations for each of your separate releases and put the connection string in the respective web..config and when you publish, that XSL transformation will put the connection string in the resulting web.config and voila.
I've done something like that recently by adding some custom configuration, which uses the host header to determine the connectionStringName, which has to be used.
EF5 has a constructor, which can handle this name
var context = new MyDbContex("name=<DBConnectionStringName>");
I just did for a project
public partial class admpDBcontext : DbContext
{
public static string name
{
get
{
if (System.Web.HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority).ToString() == "http://fcoutl.vogmtl.com")
{
return "name=admpDBcontext";
}
else { return "name=admpNyDBcontext"; }
}
}
public admpDBcontext()
: base(name)
{
}
}
And in the web.config I add the connectionString.

How to access WinRM in C#

I'd like to create a small application that can collect system information (Win32_blablabla) using WinRM as opposed to WMI. How can i do that from C#?
The main goal is to use WS-Man (WinRm) as opposed to DCOM (WMI).
I guess the easiest way would be to use WSMAN automation. Reference wsmauto.dll from windwos\system32 in your project:
then, code below should work for you. API description is here: msdn: WinRM C++ API
IWSMan wsman = new WSManClass();
IWSManConnectionOptions options = (IWSManConnectionOptions)wsman.CreateConnectionOptions();
if (options != null)
{
try
{
// options.UserName = ???;
// options.Password = ???;
IWSManSession session = (IWSManSession)wsman.CreateSession("http://<your_server_name>/wsman", 0, options);
if (session != null)
{
try
{
// retrieve the Win32_Service xml representation
var reply = session.Get("http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Service?Name=winmgmt", 0);
// parse xml and dump service name and description
var doc = new XmlDocument();
doc.LoadXml(reply);
foreach (var elementName in new string[] { "p:Caption", "p:Description" })
{
var node = doc.GetElementsByTagName(elementName)[0];
if (node != null) Console.WriteLine(node.InnerText);
}
}
finally
{
Marshal.ReleaseComObject(session);
}
}
}
finally
{
Marshal.ReleaseComObject(options);
}
}
hope this helps, regards
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Hope this helps, I've been using this for a while with my automated deployments. Please leave comments if you find issues.
I would like to note that this shows an interop error by default in Visual Studio 2010.
c.f. http://blogs.msdn.com/b/mshneer/archive/2009/12/07/interop-type-xxx-cannot-be-embedded-use-the-applicable-interface-instead.aspx
There appear to be two ways to solve this. This first is documented in the article listed above and appears to be the correct way to handle the problem. The pertinent changes for this example is:
WSMan wsManObject = new WSMan();
This is in lieu of IWSMan wsman = new WSManClass(); which will throw the error.
The second resolution is to go to the VS2010—>Solution Explorer—>Solution—>Project—>References and select WSManAutomation. Right click or hit Alt-Enter to access the properties. Change the value of the "Embed Interop Types" property of the wsmauto reference.

Programmatically access Enterprise Library Logging configuration (object model)?

I'm using Enterprise Library 3.1 and want to programmatically access the Logging Block (runtime, object model) specifically its Trace Listeners and Sources.
For example, I want to access the Filename property of a trace listener object so I can know where the log file is located on disk.
Update: Looking for answers that use the runtime object model, not by parsing the XML configuration.
You can access the logging configuration programmatically using the object model (used for configuration).
To get the specific data for the trace listener you should look at TraceListenerData (and the specific subclasses).
This example shows how to read in the configuration and then get the TraceListeners:
// Open config file
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = #"MyApp.exe.config";
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
// Get EL log settings
LoggingSettings log = config.GetSection("loggingConfiguration") as LoggingSettings;
// Get TraceListener info
foreach(TraceListenerData listener in log.TraceListeners)
{
// Check for listener types you care about
if (listener is RollingFlatFileTraceListenerData)
{
RollingFlatFileTraceListenerData data = listener as RollingFlatFileTraceListenerData;
Console.WriteLine(string.Format("Found RollingFlatFileLIstener with Name={0}, FileName={1}, Header={2}, Footer={3}, RollSizeKB={4}, TimeStampPattern={5},RollFileExistsBehavior={6}, RollInterval={7}, TraceOutputOptions={8}, Formatter={9}, Filter={10}",
data.Name, data.FileName, data.Header, data.Footer, data.RollSizeKB,
data.TimeStampPattern, data.RollFileExistsBehavior, data.RollInterval,
data.TraceOutputOptions, data.Formatter, data.Filter);
}
else // other trace listener types e.g. FlatFileTraceListenerData
{
}
}
Apparently some needed info is privately encapsulated in a LogWriterStructureHolder instance (its field is named structureHolder) on the Enterprise Library Logger.Writer instance (of Type LogWriter).
So I'm effectively looking for: Logger.Writer.structureHolder (but that field is private).
I used reflection to pull it out....
These are the significant namespaces:
using System.Reflection;
using Microsoft.Practices.EnterpriseLibrary.Logging;
This is reflection code to pull out the needed private data:
// Get the private field.
FieldInfo fiLogStructHolder
= typeof(LogWriter).GetField("structureHolder", BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic);
// Obtain field value to get the private data.
LogWriterStructureHolder structureHolder
= (LogWriterStructureHolder)fiLogStructHolder.GetValue(Logger.Writer);
// Access the value's .TraceSources property of Type Dictionary<string, LogSource>.
// The string is the name of the category from configuration.
int numSources = structureHolder.TraceSources.Count;
// Furthermore, access the listeners of any logging source by specifying:
int numListeners = structureHolder.TraceSources[0].Listeners.Count
// ^-- Note: Picked first source for example.
If anybody can find a non-private entry point for this same data please post it in an answer. Thanks.
Kudos to .NET Reflector for facilitating this answer.
public static EmailTraceListenerData GetEmailLogConfiguration()
{
var rootWebConfig1 = WebConfigurationManager.OpenWebConfiguration("/");
var section = rootWebConfig1.GetSection("loggingConfiguration");
var loggingSection = section as Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings;
if (loggingSection != null) {
// Reference to Microsoft.Practices.EnterpriseLibrary.Logging.dll and
// Microsoft.Practices.EnterpriseLibrary.Common.dll required for the code below
foreach (Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.TraceListenerData listener in loggingSection.TraceListeners) {
var emailTraceListenerData = listener as EmailTraceListenerData;
if (emailTraceListenerData != null) {
// Can obtain FromAddress, ToAddress, SmtpServer and SmtpPort
// as property of emailTraceListenerData;
return emailTraceListenerData;
}
}
}
return null;
}
Web.config file is as follow:
For Windows application, you can open the .config file withSystem.Configuration.ConfigurationManager.OpenExeConfigurationinstead of WebConfigurationManager.
The other answers seem very verbose, here is my solution:
public static TraceListenerData GetTraceListener(string name)
{
var log = ConfigurationManager.GetSection("loggingConfiguration") as LoggingSettings;
return log.TraceListeners.Single(tl => tl.Name == name);
}
Once you've called this helper method you can cast the result to whatever type the listener is such as RollingFlatFileTraceListenerData, EmailTraceListenerData, FormattedEventLogTraceListenerData, FormattedDatabaseTraceListenerData

Categories