Isolated RazorEngine failing to pass model to different AppDomain - asp.net

When I render my template without the EditHistory member, this works. However, when I add that additional member that is within my application I get an exception Could not load file or assembly 'Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. Models is the project containing ContentModel, EditHistory and UserDetail.
public class ContentModel
{
public string Html { get; set; }
public string Title { get; set; }
public EditHistory History { get; set; }
}
public class EditHistory
{
public IReadOnlyCollection<UserDetail> Authors { get; set; }
}
public class UserDetail
{
public string Name { get; set; }
public string EmailAddress { get; set; }
}
I am wrapping ContentModel in a RazorDynamicObject as such:
Razor.Run("default.cshtml", typeof(ContentModel), RazorDynamicObject.Create(cm));
As mentioned above, it works without EditHistory being present, but fails when it is.
The sandbox is set up verbatim as per how it's done at https://antaris.github.io/RazorEngine/Isolation.html
How do I get it to work with complex custom types?
Running under ASP.NET.
Edit
I have created a minimal reproduction of the issue I'm facing. It's at https://github.com/knightmeister/RazorEngineIssue. If package restore fails, manually install-package razorengine.

First of all; I was never able to get your GitHub-code running. The following is based on my own reproducing code.
I think that you're getting Could not load file or assembly-exceptions because when you setup the sandbox AppDomain you're setting:
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
This won't work in ASP.NET because assemblies are in the bin subfolder. To fix that, simply do this instead:
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
+ "\\bin";
However, ASP.NET will by default shadow copy assemblies. Therefore just doing this change will probably cause another exception:
ArgumentException: Object type cannot be converted to target type.
That's because there's a mixup between assemblies loaded in the default app domain and the sandbox. The ones in the default app domain are located in a temporary shadow copy location and the ones in the sandbox are located in the bin-folder of your web application root.
The easiest way to fix this is to disable shadow copying by adding the following line under <system.web> in your Web.config:
<hostingEnvironment shadowCopyBinAssemblies="false"/>
In addition; I think it's better and easier to skip using RazorDynamicObject and instead mark your models with [Serializable]. In fact I never got RazorDynamicObject working properly.
The rest of this answer summarizes what I did to come to this conclusion
I think that this is due to a bug or limitation in RazorEngine. (I'm not so sure about this anymore, it might very well be that shadow copying and RazorDynamicObject cannot work together)
I've spent a couple of hours trying to figure out how to get this working but I've always ended up with a security exception being thrown from RazorEngine.
There is, however, a possible workaround: Ditch RazorDynamicObject and mark your model classes as serializable.
[Serializable]
public class ContentModel
{
public string Html { get; set; }
public string Title { get; set; }
public EditHistory History { get; set; }
}
[Serializable]
public class EditHistory
{
public IReadOnlyCollection<UserDetail> Authors { get; set; }
}
[Serializable]
public class UserDetail
{
public string Name { get; set; }
public string EmailAddress { get; set; }
}
And do:
Razor.Run("default.cshtml", typeof(ContentModel), cm); // no RazorDynamicObject
I couldn't get your repro code running, so I created my own based on your code:
Create a new Console application (Visual Studio)
In the package manager console, run: install-package razorengine
Copy code from your repro:
Line 25-38 and 43-65 from:
https://github.com/knightmeister/RazorEngineIssue/blob/master/Global.asax.cs
All models from: https://github.com/knightmeister/RazorEngineIssue/blob/master/Models/Models.cs
Mark models with [Serializable].
Remove RazorDynamicObject
To ensure that we really can render user details from the authors list, change the test template to:
string template = "#Model.History.Authors[0].EmailAddress";
Also, to make that template work, change Authors in EditHistory from IReadOnlyCollection<> to IReadOnlyList<>
I created a GIST with the resulting code:
https://gist.github.com/mwikstrom/983c8f61eb10ff1e915a
This works for me. It prints hello#world.com just as it should.
ASP.NET will shadow copy assemblies by default and that will cause additional problems with sandboxing.
To get this working under ASP.NET you'll have to do the following changes:
Disable ASP.NET shadow copying by adding the following under <system.web> in your Web.config file:
<hostingEnvironment shadowCopyBinAssemblies="false"/>
Append \bin to the sandbox's application base path. So in createRazorSandbox(...) do:
adSetup.ApplicationBase =
AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\\bin";
I have tested this and it works just fine. My test project is simply:
An empty ASP.NET Web Application (created with Visual Studio), with install-package razorengine
<hostingEnvironment shadowCopyBinAssemblies="false"/> in Web.config.
The following Global.asax.cs:
https://gist.github.com/mwikstrom/ea2b90fd0d306ba3498c
There are other alternatives (besides disabling shadow copying) listed here:
https://github.com/Antaris/RazorEngine/issues/224

I mostly don't use complex types but a general rule is usually that only primitive datatypes are transferred ok (my own rule, since values often get lost for me otherwise). However, when looking at some old source code I noticed I did use many complex types, but I populated them in the Controller (e.g. in Public ActionResult Index()). After some reading I think it might work if you use something similar to this (untested, MSDN source, 2nd source):
[MetadataType(typeof(EditHistory))]
public partial class ContentModel
{
public string Html { get; set; }
public string Title { get; set; }
public EditHistory History { get; set; }
}
[MetadataType(typeof(UserDetail))]
public partial class EditHistory
{
public IReadOnlyCollection<UserDetail> Authors { get; set; }
}
public class UserDetail
{
public string Name { get; set; }
public string EmailAddress { get; set; }
}

Related

I could not connect to database instance created with Entity Framework generated from model

I created a web application and a model. Then I generated a dbcontext class and a database instance. After I built the project, I tried to connect to that database from Server Explorer in Visual Studio, but could not connect.
I tried to test connection but got an error:
This connection cannot be tested because the specified database does not exist or is not visible to the specified user
Whenever I tried to scaffold view or controller I got this error:
Unable to retrieve metadata for ... one or more validation errors were detected during model generation
ModelsTable is based on type TestModel that has no keys defined.
When I created database object in controller class and write query got same error no key defined.
Also made updates on packages and tried again. I think my connection string is correct.
Here is my model.
public class TestModel
{
[Key]
public string ID { get; } = Guid.NewGuid().ToString();
public string AreaName { get; set; }
public bool IsWorking { get; set; }
public string UserName { get; set; }
public DateTimeOffset Time { get; set; }
}
So I could not use scaffolding, Entity Framework and write query.
Here is my dbcontext class.
public class ModelDB : DbContext
{
public ModelDB()
: base("name=ModelDB")
{
}
public DbSet<TestModel> ModelsTable { get; set; }
}
I searched on internet tried founded solutions but did not understand and could not solve. I hope did not ask unnecessary questions. Thanks for your helping.
Are you using Code First? If so I think you need to generate migrations.
In visual studio go to Package Manager Console and run this commands:
Add-Migration "modelClassName"
Update-Database –Verbose
For more information refer to this link: https://msdn.microsoft.com/en-us/library/jj591621(v=vs.113).aspx
You are missing the set; in the field ID.

Creating table Entity Framework Core and SQLite

Using Microsoft.EntityFrameworkCore.SQLite, I'm attempting to create a code level creation of a database, and add a simple row to a table. I get the error, SQLite error: no such table Jumplists.
From last to first, here are the classes
using JumpList_To_Clipboard.Data.Tables;
using Microsoft.EntityFrameworkCore;
namespace JumpList_To_Clipboard.Data
{
public class DataSQLite : IData
{
public const string DATABASE = "data.sqlite";
public DataSQLite()
{
using (var db = new SQLiteDbContext(DATABASE))
{
// Ensure database is created with all changes to tables applied
db.Database.Migrate();
db.JumpLists.Add(new JumpList { Name = "Default" });
db.SaveChanges(); // Exception thrown here
}
}
}
}
The DbContext class
using JumpList_To_Clipboard.Data.Tables;
using Microsoft.EntityFrameworkCore;
namespace JumpList_To_Clipboard.Data
{
class SQLiteDbContext : DbContext
{
readonly string db_path;
public DbSet<JumpList> JumpLists { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Item> Items { get; set; }
public SQLiteDbContext(string database) : base()
{
db_path = database;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(string.Format("Data Source={0}", db_path));
}
}
}
The JumpList class
using System.Collections.Generic;
namespace JumpList_To_Clipboard.Data.Tables
{
public class JumpList
{
public int JumpListId { get; set; }
public string Name { get; set; }
public List<Group> Groups { get; set; }
public List<Item> Items { get; set; }
}
}
The other two classes aren't worth repeating here, and don't give errors.
When I use the firefox sqlite extension to look at the data.sqlite file, none of my three tables are listed.
The command db.DataBase.Migrate says it
Applies any pending migrations for the context to the database.
What are pending migrations? I can't seem to find any documentation anywhere on these.
I'm combining examples from:
https://learn.microsoft.com/en-us/ef/core/get-started/netcore/new-db-sqlite
https://blogs.msdn.microsoft.com/dotnet/2016/09/29/implementing-seeding-custom-conventions-and-interceptors-in-ef-core-1-0/
Edit: If I replace db.Database.Migrate(); with db.Database.EnsureCreated(); it works. From the documentation, Migrate() is the same, but lets you create updates to the table structures, where EnsureCreated() does not. I'm confused.
So,
Microsoft has a serious issue making decent documentation, but I did find a site that has somewhat dated documentation for Learning Entity Framework Core, specifically migrations which is in the link.
At the top, it mentions,
If you have Visual Studio, you can use the Package Manager Console (PMC) to manage migrations.
Which led to the Package Manager Console page which states right at the top, that you need to have:
If you want to use the Package Manager Console to execute migrations command, you need to ensure that the latest version of Microsoft.EntityFrameworkCore.Tools is added to your project.json file.
The problem is, there is no project.json file anywhere in my project (or solution). After some searching, I found that via NuGet, to add Microsoft.EntityFrameworkCore.Tools
Then via Tools > NuGet Package Manager > Package Manager Console I was able to run the add-migration InitialDatabases command. The last part InitialDatabases is the name of the class it creates for you, and sticks in a folder called Migrations at the base of the project.
Now when:
context.Database.Migrate();
is run, all is well!
Try this (worked for me in a project a few months ago, i don't remember why):
public virtual DbSet<JumpList> JumpLists { get; set; }
public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<Item> Items { get; set; }
Also i had to use LONG instead of INT for classes ID because sqlite uses LONG as default for table ID, so after when you do a CRUD operation it fails because it can't compare/convert/cast LONG(64) to INT(32).

Rename Navigation Property in Entity Framework 5 does not update code

I would like to rename a navigation property and access that object in code by the new name I gave. I have renamed one of my user properties to a more meaningful name rather than 1,2,3...etc.
In this example, I have renamed OGSMUser5 to OGSMUserResponsible in the model diagram.
I then tried to access this in code, but it can't find my renamed property OGSMUserResponsible.
#contImprovement.OGSMUserResponsible.FullName
When I look at the code in the model generated from Entity Data Model, I see that the code did not change for the newly renamed model, which explains why I can't access it.
public virtual OGSMUser OGSMUser { get; set; }
public virtual OGSMUser OGSMUser1 { get; set; }
public virtual OGSMUser OGSMUser2 { get; set; }
public virtual OGSMUser OGSMUser3 { get; set; }
public virtual OGSMUser OGSMUser4 { get; set; }
public virtual OGSMUser OGSMUser5 { get; set; }
I have seen somewhat similar questions to this, but the answers I have seen I can't believe is the only option. They have been manually creating a new property with the name you want (what I renamed mine to in the navigation property) in a partial class and return the navigation property with the number in the name to help clean it up. I can't believe that this is the best way to accomplish what I am looking for.
Thanks for the help.
You probably didn't run the T4 template transformations.
Just right click your .tt files (they are under your .edmx file in the Solution Explorer tree) and run them. Alternatively, you can run all tt by clicking the button in Visual Studio's toolbar.

How can I name my Database using EF Code First?

I've got my EF Code First working exactly as expected aside from one small bit. I'm not sure how to name my Database File.
I'm using SQL CE, but I'm sure this applies to all forms of EF Code First.
Here's my DbContext
namespace MyApp.Domain.EntityFramework
{
public class DataContext : DbContext
{
//...
}
}
And when the database is created it's created as
MyApp.Domain.EntityFramework.DataContext.sdf
I'd prefer to just have it named
MyApp.sdf
Now I'm sure this is simple, but my Googling skills keep turning up examples where the database name is auto generated like mine.
http://www.hanselman.com/blog/SimpleCodeFirstWithEntityFramework4MagicUnicornFeatureCTP4.aspx
You need to specify a connection string (for example by creating a connection string named DataContext (your class name) in your config file, and set the desired name there.
I was looking to do the same. Managed to end up with this:
public class ShopDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Feature> Features { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Subcategory> Subcategories { get; set; }
public DbSet<Information> OrderInformation { get; set; }
public ShopDbContext() : base("Shop")
{
}
}
It will name your database "Shop" so just replace what is in the base("Shop") with whatever you want to call your database. Hope this helps.

Entity Framework 4.1 Code First: Advice on persisting data from external source?

Part of my project is to persist data from another source. In this case we have an SAP data source that we will need to pull data from. I need to take the data from SAP and map it to entities I have in my application. Here is an example of an entity I have in my application:
public class Project : BaseEntity
{
public string Name { get; set; }
public string ProjectNumber { get; set; }
public string Description { get; set; }
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
public string Currency { get; set; }
#region Navigation Properties
public virtual Address Address { get; set; }
public virtual CompanyCode CompanyCode { get; set; }
public virtual ICollection<Contact> TeamMembers { get; set; }
#endregion
}
As you can see, I have child objects that I map from SAP as well. I need some advice on the best way to insert and update my entities. I am struggling with knowing when to add (insert) entities to my context and when to attach (update) them, because SAP doesn't have knowledge of what my application may or may not have. I need to guard against duplicates, too. For example, should I perform a lookup of each child entity in my parent entity to see if they exist before I apply them to the parent? Then, add / attach the entire parent object to the context or handle each entity separately while still maintaing their relationships?
Yes you must manually test everything to make correct decision what must be inserted, updated or deleted. Depending on the application you can use some more complex queries to reduce number of round trips to the database - for example you can use single query with Contains to load all TeamMembers needed for processed Project or you can load Project with including all related data if you also need to test if project exists.
I did large synchronization application before and I end up with pre-loading all entities at the beginning with few queries and working completely in memory.
Don't forget to use DbSet's Local property or Find method to take advantage of already loaded entities.
You can also use some custom stored procedures to improve performance of this operation.

Resources