Why does XamlReader throw when I use a ParserContext? - wpf

This works:
XamlReader.Parse("<Pig xmlns=\"clr-namespace:Farm;assembly=Farm\"/>");
This throws The tag 'Pig' does not exist in XML namespace 'clr-namespace:Farm;assembly=Farm':
var context = new ParserContext();
context.XmlnsDictionary.Add("", "clr-namespace:Farm;assembly=Farm");
XamlReader.Parse("<Pig/>", context);
Why?
Farm is the calling application.

What you have will work in .NET 4.0, but unfortunately not in .NET 3.5. Try using XamlTypeMapper instead:
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[] { });
context.XamlTypeMapper.AddMappingProcessingInstruction("", "Farm", "Farm");
XamlReader.Parse("<Pig/>", context);
If you wanted to use a namespace prefix, you could declare a clr namespace to xml namespace mapping with the XamlTypeMapper and then declare a namespace prefix for the xml namespace.
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[] { });
context.XamlTypeMapper.AddMappingProcessingInstruction("Foo", "Farm", "Farm");
context.XmlnsDictionary.Add("a", "Foo");
XamlReader.Parse("<a:Pig/>", context);

Related

Injecting serverside data when using ISpaBuilder.UseReactDevelopmentServer

When using ASP.NET (Core, .NET 5) MVC's IApplicationBuilder.UseSpa / ISpaBuilder.UseReactDevelopmentServer (in development), is there a way to postprocess the index HTML before it's sent to the browser? I need to inject a script tag holding data about the currently auth'd user to be consumed by the React app.
I want to avoid having to do an extra call from inside my React app just to get the currently logged on user at startup.
You can use custom middleware to do that. Assuming you're on .net core 3.1, the middleware would look something along these lines:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace TestReactDevServer.Middleware
{
public class ScriptInjectorMiddleware
{
private readonly RequestDelegate _next;
public ScriptInjectorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
//Save pointer to the original response body stream
var originalBodyStream = context.Response.Body;
//Create new memory stream
using (var responseBody = new MemoryStream())
{
//...and use it for subsequent requests so we can peek the contents
context.Response.Body = responseBody;
//Continue down the Middleware pipeline, eventually returning to this class
await _next(context);
//inspect response, inject script
await InjectScript(responseBody, "window.myUser='123';");
//copy the contents of the new memory stream to the original place
await responseBody.CopyToAsync(originalBodyStream);
}
}
private async Task<Stream> InjectScript(Stream input, string script)
{
input.Seek(0, SeekOrigin.Begin);
var decompressed = new MemoryStream();
using (var tmp = new GZipStream(input, CompressionMode.Decompress, true))
{
tmp.CopyTo(decompressed);
}
var html = await decompressed.StreamToString();
var modifiedHtml = Regex.Replace(html, "</body>[\\n\\r]+</html>", $"<script type=\"text/javascript\">{script}</script></body></html>", RegexOptions.IgnoreCase | RegexOptions.Multiline); // any way to locate closing tags will work here, you probably can be more efficient
input.Seek(0, SeekOrigin.Begin);
using (var modifiedHtmlStream = modifiedHtml.ToStream())
using (var tmp = new GZipStream(input, CompressionMode.Compress, true)) // might be optional
{
modifiedHtmlStream.CopyTo(tmp);
}
return input;
}
}
public static class ScriptInjectorMiddlewareExtensions
{
public static IApplicationBuilder UseScriptInjectorMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<ScriptInjectorMiddleware>();
}
}
public static class StreamExtensions {
public static async Task<string> StreamToString(this Stream stream) {
stream.Seek(0, SeekOrigin.Begin);
return await new StreamReader(stream).ReadToEndAsync();
}
public static Stream ToStream(this string str)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
}
a couple of things to point out:
Testing this with Chrome, I ended up having to decompress the proxied response and compress it back after modification - I think compression step might be optional.
Depending on your user agent you might need to handle more compression cases (see more examples on Github)
You will need to inject this middleware before your call to .UseSpa() in Startup.cs: adding app.UseUserInjectorMiddleware(); should pick up the included extension method
I suspect this example is far from being complete, especially in terms of handling different encodings and content types - I am hoping you'd be able to adapt the idea to your use case.

Creating new Role (by code) during the Tenant creation process from UI (ABP.IO)

I am trying to add the creation of roles while I create a new Tenant from the UI on ABP.IO Framework version 4.
From ABP.IO documentation, I found that by using the existing class SaasDataSeedContributor I can "seed" some datas while I am creating a new Tenant.
My issue is that from this class, I do not have permission to use IIdentityRoleAppService.CreateAsync method (Given policy has not granted).
So I tried to go through an AppService and use IdentityRoleManager or even IIdentityRoleRepository,but it is not possible to create IdentityRole object as the constructor is inaccessible due to his protection level.
Any thought about it? Is there any another way to do action while creating a tenant appart using SaasDataSeedContributor. Or maybe I am doing something wrong here.
Thanks for your help
Try this.
public class AppRolesDataSeedContributor : IDataSeedContributor, ITransientDependency
{
private readonly IGuidGenerator _guidGenerator;
private readonly IdentityRoleManager _identityRoleManager;
public AppRolesDataSeedContributor(IGuidGenerator guidGenerator, IdentityRoleManager identityRoleManager)
{
_guidGenerator = guidGenerator;
_identityRoleManager = identityRoleManager;
}
public async Task SeedAsync(DataSeedContext context)
{
if (context.TenantId.HasValue)
{
// try this for a single known role
var role = await _identityRoleManager.FindByNameAsync("new_role");
if (role == null)
{
var identityResult = await _identityRoleManager.CreateAsync(
new IdentityRole(_guidGenerator.Create(), "new_role", context.TenantId.Value));
}
// or this (not tested) for multiple roles
/*
var newRoles = new[] { "role1", "role2" };
var identityRoles = from r
in _identityRoleManager.Roles
where r.TenantId == context.TenantId.Value
select r.Name;
var except = newRoles.Except(identityRoles.ToList());
foreach (var name in except)
{
var identityResult = await _identityRoleManager.CreateAsync(
new IdentityRole(_guidGenerator.Create(), name, context.TenantId.Value));
}
*/
}
}
}

About load supported cultures from DB in .NET CORE

I have a Language entity with all supported languages in my db, each language has a culture string attribute. I want to load supported cultures from DB.
In my service initializer I have it:
public void ConfigureServices(IServiceCollection services)
{
// ... previous configuration not shown
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-GB"),
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
};
opts.DefaultRequestCulture = new RequestCulture("en-GB");
// Formatting numbers, dates, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized.
opts.SupportedUICultures = supportedCultures;
});
}
How I can access my DB context inside it?
There is any other better way to do it?
I don't think there's an out of the box solution for this.
However, you can implement your own middleware that achieves this by using ASP.Net's RequestLocalizationMiddleware:
public class CustomRequestLocalizationMiddleware
{
private readonly RequestDelegate next;
private readonly ILoggerFactory loggerFactory;
public CustomRequestLocalizationMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
this.next = next;
this.loggerFactory = loggerFactory;
}
public async Task Invoke(HttpContext context /* You can inject services here, such as DbContext or IDbConnection*/)
{
// You can search your database for your supported and/or default languages here
// This query will execute for all requests, so consider using caching
var cultures = await Task.FromResult(new[] { "en" });
var defaultCulture = await Task.FromResult("en");
// You can configure the options here as you would do by calling services.Configure<RequestLocalizationOptions>()
var options = new RequestLocalizationOptions()
.AddSupportedCultures(cultures)
.AddSupportedUICultures(cultures)
.SetDefaultCulture(defaultCulture);
// Finally, we instantiate ASP.Net's default RequestLocalizationMiddleware and call it
var defaultImplementation = new RequestLocalizationMiddleware(next, Options.Create(options), loggerFactory);
await defaultImplementation.Invoke(context);
}
}
Then, we inject the required services and use the custom middleware in Startup.cs or Program.cs as follows:
services.AddLocalization()
/* ... */
app.UseMiddleware<CustomRequestLocalizationMiddleware>()
Do not call app.UseRequestLocalization(), because this would call ASP.Net's RequestLocalizationMiddleware again with the default options, and override the culture that has been resolved previously.

SQL Server Always Encrypted with .NET Core not compatible

I'm trying to use the Always Encrypted feature of SQL Server 2016 with .NET Core and seems like it can not be used (yet). Trying to import the Microsoft.SqlServer.Management.AlwaysEncrypted.AzureKeyVaultProvider from Nuget, I get an error stating it is not compatible:
Package Microsoft.SqlServer.Management.AlwaysEncrypted.AzureKeyVaultProvider 1.0.201501028 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6)
Any ideas on how/where to get a compatible version?
Always Encrypted is now supported in .Net Core 3.1 LTS.
You have to use the Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider nuget package
Install-Package Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider -Version 1.1.1
Make sure you have a Keyvault setup.
FOR DEBUGGING your account in VS has to have sufficent rights to access the keyvault. (When published the app itself has to have sufficent rights : see https://learn.microsoft.com/en-us/azure/key-vault/managed-identity) Get and List permissions alone might not be sufficient.
Then in program.cs :
using Microsoft.AspNetCore.Hosting;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
using Microsoft.Extensions.Hosting;
//namespaces etc omitted
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var keyVaultEndpoint = GetKeyVaultEndpoint();
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
config.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders: new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
{
{
SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider
}
});
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
private static string GetKeyVaultEndpoint() => "https://YOURKEYVAULT.vault.azure.net/";
}
In StartUp.cs ConfigureServices:
using Microsoft.Data.SqlClient;
//Code omitted
services.AddDbContext<EnitiesModel>(options =>
options.UseSqlServer(new SqlConnection(Configuration.GetConnectionString("EntitiesModel"))));
Make sure your connectionstring contains the Column Encryption Setting=Enabled parameter:
"ConnectionStrings": {
"EntitiesModel": "Server=SOMESERVER.database.windows.net;Database=SOMEDB;Trusted_Connection=False;Encrypt=True;Integrated Security=False;
MultipleActiveResultSets=true;persist security info=True;user id=SOMEDBACCOUNT;password=SOMEPASSWORD;
Column Encryption Setting=enabled;"
}
Small gotcha : If you used DB scaffolding make sure the Model connectionstring has the Column Encryption Setting aswell!
(if you did not change it, it is standard inside the DBModel class after scaffolding with a VS warning)
This should get you up and running...
Disclaimer: I am a Program Manager at Microsoft
Always Encrypted is currently not supported in .NET Core. It is on our roadmap, we don't have a timeline for it yet.
This is now supported. See answers below.
It's now supported in .NET Core 3.0 Preview 5, which provides a new SqlClient supporting Always Encrypted and more. See this comment for more info.
For the Key Vault provider, you need to use Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider instead.
A variant of the Program.cs from Tim's answer above, but for apps registered with Azure App Registration:
namespace Sample
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var keyVaultEndpoint = GetKeyVaultEndpoint();
if (!string.IsNullOrEmpty(keyVaultEndpoint))
{
var azureServiceTokenProvider = new AzureServiceTokenProvider(keyVaultEndpoint);
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders: new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
{
{
SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider
}
});
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
private static string GetKeyVaultEndpoint() => "RunAs=App;AppId=<app ID>;TenantId=<tenant ID>.onmicrosoft.com;AppKey=<app secret>";
}
}

Is RecreateDatabaseIfModelChanges available in WPF?

I'm trying out Entity Framework Code First. I can't seem to find the assembly/namespace to use for RecreateDatabaseIfModelChanges in WPF 4.0. Is this an ASP.NET-only feature? If not, what assembly should I reference?
Here's my code:
using System;
using System.Data.Entity;
using System.Windows;
using CodeFirstTester.Models;
namespace CodeFirstTester
{
public partial class App : Application
{
static App()
{
// this fails:
Database.SetInitializer(new RecreateDatabaseIfModelChanges<NerdDinners>());
// The type or namespace name 'RecreateDatabaseIfModelChanges'
// could not be found (are you missing a using directive or
// an assembly reference?)
using (var nerdDinners = new NerdDinners())
{
var dinner = new Dinner()
{
Title = "Party at Scott's House",
EventDate = DateTime.Parse("12/31/2010"),
Address = "Building 40",
HostedBy = "scottgu#microsoft.com"
};
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
}
}
}
}
The initializer is called DropCreateDatabaseIfModelChanges. It can be found in EntityFramework.dll (EF 4.1) in System.Data.Entity namespace.

Resources