How to re-use a web page in different web applications - asp.net

I created two web solutions in ASP.NET 4.0, A and B. And I added a data entry web form (DataEntery.aspx) into Solution A, with third party user controls and form authentication.
I want to re-use the web page DataEntery.aspx into solution B. What is the best method for it?

You could create a third web application called Solution C that will hold user controls with shared functionality. Than add a reference to Solution C in Solution A and B.
Example: HOW TO: Share ASP.NET Pages and User Controls Between Applications by Using Visual Basic .NET

One of the way to do this is to add you DataEntry.aspx and code behind classes as Link to your projects. Also, you will need to add logic to copy DataEntry.aspx page from shared directory where it is located to directory it is added to project. You can do this by writing command lines command in PreBuild event of your projects or create CopyLinkedContentFiles.targets file with the following content:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDependsOn>
CopyLinkedContentFiles;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
<Target Name="CopyLinkedContentFiles">
<!-- Remove any old copies of the files -->
<Delete Condition=" '%(Content.Link)' != '' AND Exists('$(WebProjectOutputDir)\%(Content.Link)') "
Files="$(WebProjectOutputDir)\%(Content.Link)" />
<!-- Copy linked content files recursively to the project folder -->
<Copy Condition=" '%(Content.Link)' != '' " SourceFiles="%(Content.Identity)"
DestinationFiles="$(WebProjectOutputDir)\%(Content.Link)" />
</Target>
</Project>
And then include this target to your project which will reference to page added as link. You can do by adding the following lines to your web applications csproj files:
<Import Project="$(MSBuildProjectDirectory)\[RELATIVE PATH TO FILE]\CopyLinkedContentFiles.targets" />
This line should be added for example after this line in your csproj files:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

The best method is to create a dll that has your web pages.
You can reference this dll to get the code included in your project.
You can load resources embedded in a dll by using the VirtualFile class. Here is an example
public class SVirtualFile : VirtualFile
{
private string m_path;
public SVirtualFile(string virtualPath)
: base(virtualPath)
{
m_path = VirtualPathUtility.ToAppRelative(virtualPath);
}
public override System.IO.Stream Open()
{
var parts = m_path.Split('/');
var assemblyName = parts[1];
var resourceName = parts[2];
assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName);
var assembly = System.Reflection.Assembly.LoadFile(assemblyName + ".dll");
if (assembly != null)
{
return assembly.GetManifestResourceStream(resourceName);
}
return null;
}
}
You need to hook into the application life cycle and register a VirtualPathProvider so that asp.net knows you are serving virtual files.
public static class AppStart
{
public static void AppInitialize()
{
SVirtualPathProvider sampleProvider = new SVirtualPathProvider ();
HostingEnvironment.RegisterVirtualPathProvider(sampleProvider);
}
}
public class SVirtualPathProvider : VirtualPathProvider
{
public SVirtualPathProvider()
{
}
private bool IsEmbeddedResourcePath(string virtualPath)
{
var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/Succeed.Web/", StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return IsEmbeddedResourcePath(virtualPath) || base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsEmbeddedResourcePath(virtualPath))
{
return new SVirtualFile(virtualPath);
}
else
{
return base.GetFile(virtualPath);
}
}
public override CacheDependency GetCacheDependency( string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
if (IsEmbeddedResourcePath(virtualPath))
{
return null;
}
else
{
return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
}
}
Refer:
How to use virtual path providers to dynamically load and compile content from virtual paths

Related

Appsettings in .NET Core vs Webforms

I have a library which I use in both an ASP.NET app and a .NET Core app.
In both apps, I need to load settings from web.config(asp) in a virtual directory /CMSContent/Settings/web.config and appsettings.json(core).
I set an enviromentvariable in both apps named SystemType to WebForms(asp) and .NET Core (core), and build a function which reads data in the config file.
public static string SolutionDB()
{
string SystemType = System.Environment.GetEnvironmentVariable("SystemType", EnvironmentVariableTarget.Process);
switch (SystemType)
{
case "NetCore":
using (System.IO.StreamReader sr = new System.IO.StreamReader("appsettings.json", Encoding.UTF8))
{
var json = sr.ReadToEnd();
}
return "ComitoCMS_1";
case "WebForms":
System.Configuration.Configuration config = WebConfigurationManager.OpenWebConfiguration("/CMSContent/Settings/");
return config.AppSettings.Settings["SolutionDB"].Value;
break;
default:
return string.empty;
}
return string.empty;
}
When accessing the function from .net core it always returns the error:
TypeLoadException: Could not load type 'System.Web.Configuration.WebConfigurationManager' from assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Even though the code doesn't get into the case "WebForms".
Is there any other way to read either web.config when running the asp.net app and from appsettings.json when running the .net core app
I would suggest to create library with an abstraction. For example ConfigurationValueProvider class.
public abstract class ConfigurationValueProvider
{
public abstract GetValue(string key);
}
Then create another two libvraries. One with implementation for .NET Core and second with implementation for WebForms.
NET Core
public class AppSettingsValueProvider : ConfigurationValueProvider
{
public override GetValue(string key)
{
// Load value for NET Core apps
}
}
WebForms
public class WebConfigValueProvider : ConfigurationValueProvider
{
public override GetValue(string key)
{
// Load value for WebForms apps
}
}
Each project type should reference just the one it is supposed to be used.
It is an idea how to do it. You should change it according to your needs.

DotNet Core - Connection String from Class Library

I have my connection string to SQL stored in the Web project in appsettings.json
"ConnectionStrings": {
"MyDbConnectionString": "***"
},
Then I added a DB context using Scaffold
Scaffold-DbContext -Connection "name=MyDbConnectionString" -Provider "Microsoft.EntityFrameworkCore.SqlServer" ... -Force
I can use the context in a controller and I have no issues getting data or writing. However, I would like all my business logic to be on a separate class library. So here is my repository from my Library:
public class MyRepository
{
private static MyContext CurrentContext
{
get { return new MyContext(); }
}
public static async void AddEventLog(EventLog eventLog)
{
using (var context = CurrentContext)
{
context.EventLog.Add(eventLog);
await context.SaveChangesAsync();
}
}
}
But it fails when it tries to write to the DB.
System.InvalidOperationException: 'A named connection string was used, but the name 'MyDbConnectionString' was not found in the application's configuration.
Should I be adding appsettings.json to the library project (This seems redundant, and incorrect)? What am I missing? How do I reference back to the web projects appsettings.json file?
Any help would be greatly appreciated.
Here is my startup
services.AddDbContext<MyContext>(options =>options.UseSqlServer(Configuration.GetConnectionString("MyDbConnectionString")));
***** HERE ARE CHANGES I HAVE MADE TO THE WORK *****
I have found the issue I believe so here we go.
Remove the following from MySsdCaseContext.
public MySsdCaseContext()
{
}
and keep this one..
public MySsdCaseContext(DbContextOptions<MySsdCaseContext> options) : base(options)
{
}
For the purposes of fixing this comment out the following from OnConfiguring.
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("name=MySsdCaseDb");
}
In startup.cs add the following inside ConfigureService method.
services.AddDbContext<MySsdCaseContext>(options
=>options.UseSqlServer(Configuration.GetConnectionString("MySsdCaseDb")));
This should prompt you to add a reference to MySsdCase.Core.Data class library. You don't currently have this. Basically put
the following at the top of startup.cs
using MySsdCase.Core.Data;
Ensure the following is inside MySsdCase.Web.cspoj
<ItemGroup>
<ProjectReference Include="..\MySsdCase.Core\MySsdCase.Core.csproj" />
</ItemGroup>
Do it like this...
public class EventLogRepository
{
private readonly MySsdCaseContext _context;
public async Task AddEventLogAsync(EventLog eventLog)
{
var myVar = await _context.Set<ClientDetails>()
.AsNoTracking()
.Select(p => p)
.Take(2)
.ToListAsync();
}
}
I think overall there was no reference to the DAL from the BL in startup.cs.

ASP.NET MVC Bundle each file seperate in debug mode

How render each bundled file separate in debug mode?
I want to have rendered css and js file separate in my view, because it is hard to debug javascript files when all files are bundled.
You need to create simple BundleHelper class.
After that in your *.cshtml file yo can use
#BundleHelper.RenderScripts("~/bundles/js")
#BundleHelper.RenderStyles("~/bundles/style")
public class BundleHelper
{
private static IEnumerable<string> GetOriginalFilePaths(string bundlePath)
{
var resolver = new BundleResolver(BundleTable.Bundles);
IEnumerable<string> scriptPaths = resolver.GetBundleContents(bundlePath).ToList();
return scriptPaths;
}
public static IHtmlString RenderScripts(string bundlePath )
{
if (BundleTable.EnableOptimizations) return Scripts.Render(bundlePath);
var scriptPaths = GetOriginalFilePaths(bundlePath);
return Scripts.Render(scriptPaths.ToArray());
}
public static IHtmlString RenderStyles(string bundlePath)
{
if (BundleTable.EnableOptimizations) return Styles.Render(bundlePath);
var stylePaths = GetOriginalFilePaths(bundlePath);
return Styles.Render(stylePaths.ToArray());
}
}
In production it will work as you want and in development will work like as you haven't used bundling.

Assembly resolving in ASP.NET project outside of bin folder

How can i resolve assemblies references outside of bin folder of ASP.NET web-development server? This can be useful to not have copies of same dll's.
Nothing is working: probing element at web.config don't work, i can't set up domain because it do application manager, and i can't subscribe on the resolve assembly event because it's too late - when i can subscribe initialization is over. So what can i do?
We can use PreApplicationStartMethodAttribute
and mark them some public static void method(in web-project assembly) with no arguments. This can be done at AssemblyInfo.cs class
For example:
[assembly: PreApplicationStartMethod(
typeof(Web.Initializer), "Initialize")]
That method will be called before compilation but after processing of the web.config. So we must explicitly tell to the compiler witch assembly it need to use during compilation. Also we need to subscribe here on Assembly Resolve event so we can manage assemblies resolving. Here is example:
public static class Initializer
{
public static void Initialize()
{
AppDomain.CurrentDomain.AssemblyResolve += LoadFromCommonBinFolder;
var referAsm = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
foreach (var assemblyName in referAsm)
{
try
{
var curAsm = Assembly.Load(assemblyName);
BuildManager.AddReferencedAssembly(curAsm);
LoadChildReferences(curAsm);
}
catch {}
}
}
private static void LoadChildReferences(Assembly curAsm)
{
foreach (var assemblyName in curAsm.GetReferencedAssemblies())
{
try
{
BuildManager.AddReferencedAssembly(Assembly.Load(assemblyName));
}
catch {}
}
}
private static Assembly LoadFromCommonBinFolder(object sender, ResolveEventArgs args)
{
string commonBinFolder = System.Configuration.ConfigurationManager.AppSettings["CommonBinFolderPath"];
if (String.IsNullOrEmpty(commonBinFolder))
{
throw new InvalidOperationException("​​CommonBinFolderPath in the app.config isn't seted.");
}
string assemblyName = new AssemblyName(args.Name).Name;
string assemblyPath = Path.Combine(commonBinFolder, assemblyName);
if (!File.Exists(assemblyPath + ".dll"))
{
if (!File.Exists(assemblyPath + ".exe"))
{
//searching for resources
var ci = CultureInfo.CurrentUICulture;
assemblyPath = Path.Combine(commonBinFolder, ci.Name, assemblyName + ".dll");
if (!File.Exists(assemblyPath))
{
assemblyPath = Path.Combine(commonBinFolder, ci.Parent, assemblyName + ".dll");
if (!File.Exists(assemblyPath))
{
return null;
}
}
}
}
return Assembly.LoadFrom(assemblyPath);
}
}
At this case "Web.Project.Assembly" still must be located in the bin folder. Others assemblies can shared from any folder.
Assemblies that are included under compilation Element in the web.config file must be also in the bin folder or at sub folder with probing element setted.
In same cases we must also add to this code adding references to child assemblies.
Why use 'BuildManager.AddReferencedAssembly'?
In the 'Application_Start' method to bind event the 'AssemblyResolve' and set the Inherits with assembly name in the aspx page,no 'BuildManager.AddReferencedAssembly'.

How do I define custom web.config sections with potential child elements and attributes for the properties?

The web applications I develop often require co-dependent configuration settings and there are also settings that have to change as we move between each of our environments.
All our settings are currently simple key-value pairs but it would be useful to create custom config sections so that it is obvious when two values need to change together or when the settings need to change for an environment.
What's the best way to create custom config sections and are there any special considerations to make when retrieving the values?
Using attributes, child config sections and constraints
There is also the possibility to use attributes which automatically takes care of the plumbing, as well as providing the ability to easily add constraints.
I here present an example from code I use myself in one of my sites. With a constraint I dictate the maximum amount of disk space any one user is allowed to use.
MailCenterConfiguration.cs:
namespace Ani {
public sealed class MailCenterConfiguration : ConfigurationSection
{
[ConfigurationProperty("userDiskSpace", IsRequired = true)]
[IntegerValidator(MinValue = 0, MaxValue = 1000000)]
public int UserDiskSpace
{
get { return (int)base["userDiskSpace"]; }
set { base["userDiskSpace"] = value; }
}
}
}
This is set up in web.config like so
<configSections>
<!-- Mailcenter configuration file -->
<section name="mailCenter" type="Ani.MailCenterConfiguration" requirePermission="false"/>
</configSections>
...
<mailCenter userDiskSpace="25000">
<mail
host="my.hostname.com"
port="366" />
</mailCenter>
Child elements
The child xml element mail is created in the same .cs file as the one above. Here I've added constraints on the port. If the port is assigned a value not in this range the runtime will complain when the config is loaded.
MailCenterConfiguration.cs:
public sealed class MailCenterConfiguration : ConfigurationSection
{
[ConfigurationProperty("mail", IsRequired=true)]
public MailElement Mail
{
get { return (MailElement)base["mail"]; }
set { base["mail"] = value; }
}
public class MailElement : ConfigurationElement
{
[ConfigurationProperty("host", IsRequired = true)]
public string Host
{
get { return (string)base["host"]; }
set { base["host"] = value; }
}
[ConfigurationProperty("port", IsRequired = true)]
[IntegerValidator(MinValue = 0, MaxValue = 65535)]
public int Port
{
get { return (int)base["port"]; }
set { base["port"] = value; }
}
Use
To then use it practically in code, all you have to do is instantiate the MailCenterConfigurationObject, this will automatically read the relevant sections from web.config.
MailCenterConfiguration.cs
private static MailCenterConfiguration instance = null;
public static MailCenterConfiguration Instance
{
get
{
if (instance == null)
{
instance = (MailCenterConfiguration)WebConfigurationManager.GetSection("mailCenter");
}
return instance;
}
}
AnotherFile.cs
public void SendMail()
{
MailCenterConfiguration conf = MailCenterConfiguration.Instance;
SmtpClient smtpClient = new SmtpClient(conf.Mail.Host, conf.Mail.Port);
}
Check for validity
I previously mentioned that the runtime will complain when the configuration is loaded and some data does not comply to the rules you have set up (e.g. in MailCenterConfiguration.cs). I tend to want to know these things as soon as possible when my site fires up. One way to solve this is load the configuration in _Global.asax.cx.Application_Start_ , if the configuration is invalid you will be notified of this with the means of an exception. Your site won't start and instead you will be presented detailed exception information in the Yellow screen of death.
Global.asax.cs
protected void Application_ Start(object sender, EventArgs e)
{
MailCenterConfiguration.Instance;
}
Quick'n Dirty:
First create your ConfigurationSection and ConfigurationElement classes:
public class MyStuffSection : ConfigurationSection
{
ConfigurationProperty _MyStuffElement;
public MyStuffSection()
{
_MyStuffElement = new ConfigurationProperty("MyStuff", typeof(MyStuffElement), null);
this.Properties.Add(_MyStuffElement);
}
public MyStuffElement MyStuff
{
get
{
return this[_MyStuffElement] as MyStuffElement;
}
}
}
public class MyStuffElement : ConfigurationElement
{
ConfigurationProperty _SomeStuff;
public MyStuffElement()
{
_SomeStuff = new ConfigurationProperty("SomeStuff", typeof(string), "<UNDEFINED>");
this.Properties.Add(_SomeStuff);
}
public string SomeStuff
{
get
{
return (String)this[_SomeStuff];
}
}
}
Then let the framework know how to handle your configuration classes in web.config:
<configuration>
<configSections>
<section name="MyStuffSection" type="MyWeb.Configuration.MyStuffSection" />
</configSections>
...
And actually add your own section below:
<MyStuffSection>
<MyStuff SomeStuff="Hey There!" />
</MyStuffSection>
Then you can use it in your code thus:
MyWeb.Configuration.MyStuffSection configSection = ConfigurationManager.GetSection("MyStuffSection") as MyWeb.Configuration.MyStuffSection;
if (configSection != null && configSection.MyStuff != null)
{
Response.Write(configSection.MyStuff.SomeStuff);
}
The custom configuration are quite handy thing and often applications end up with a demand for an extendable solution.
For .NET 1.1 please refer the article https://web.archive.org/web/20211027113329/http://aspnet.4guysfromrolla.com/articles/020707-1.aspx
Note: The above solution works for .NET 2.0 as well.
For .NET 2.0 specific solution, please refer the article https://web.archive.org/web/20210802144254/https://aspnet.4guysfromrolla.com/articles/032807-1.aspx
You can accomplish this with Section Handlers. There is a basic overview of how to write one at http://www.codeproject.com/KB/aspnet/ConfigSections.aspx however it refers to app.config which would be pretty much the same as writing one for use in web.config. This will allow you to essentially have your own XML tree in the config file and do some more advanced configuration.
The most simple method, which I found, is using appSettings section.
Add to Web.config the following:
<appSettings>
<add key="MyProp" value="MyVal"/>
</appSettings>
Access from your code
NameValueCollection appSettings = ConfigurationManager.AppSettings;
string myPropVal = appSettings["MyProp"];

Resources