Example ASP.Net Core Breeze server with angular/breeze client code? - breeze

Is there example code of a breeze/angular client app using ASP.Net Core Breeze server?
It looks like there are the following Nuget packages:- Breeze.AspNetCore.NetCore and Breeze.Composite.AspNetCore.EF6
It would be really helpful to have the TempHire example using this technology.
Can you point me in the right direction? re. frontend/backend code example
Any help appreciated.

This is a bit of a journey because right now there are a lot of moving parts. I have had some success in getting this to work but there are some limitations for example i cannot use .expand('entityName') on the client.
I am using .NET CORE 3.0 preview with Entity Framework Core. I have attached an image of all the dependencies i have installed. Not all of them are required but probably used in a API project.
The most important parts of the below code snippet are to setup the NewtonsoftJson settings.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
//THE BELOW LINE IS IMPORTANT OTHERWISE IT WILL CAMELCASE TO THE SERVER
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
//THE BELOW LINE PREVENTS LOOPING ENTITY REFs
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var connection = #"Server=tcp:XXXXX.database.windows.net,1433;Initial Catalog=DBNAME;Persist Security Info=False;User ID=XXX;Password=XXXXX;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
//THIS IS WHERE YOU ARE GOING TO MAKE YOUR CONTEXT INJECTABLE
services.AddDbContext<YOURCONTEXTContext>(options => options.UseSqlServer(connection, x => x.UseNetTopologySuite()));
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Token);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Then you need to setup your breeze controller:
using Microsoft.AspNetCore.Authorization;
using System.Linq;
using HB.Data.Models;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using System;
using Microsoft.AspNetCore.Mvc;
using Breeze.AspNetCore;
using HB.API.Manager;
using HB.BusinessFacade.Business;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using Breeze.Persistence;
using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace HB.API.Controllers
{
[BreezeQueryFilter]
[Route("api/[controller]/[action]")]
public class BreezeController : ControllerBase
{
private YOURCONTEXTContext _context;
private hbPersistenceManager PersistenceManager;
string UserID;
public BreezeController(YOURCONTEXTContext context, IHttpContextAccessor httpContextAccessor)
{
this._context = context;
PersistenceManager = new hbPersistenceManager(context);
//this.UserID = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
}
[HttpGet]
public string Metadata()
{
return PersistenceManager.Metadata();
}
}
And then just a little helper that i use for the PersistenceManager
using HB.Data.Models;
using Breeze.Persistence.EFCore;
namespace HB.API.Manager
{
public class hbPersistenceManager : EFPersistenceManager<YOURCONTEXTContext>
{
public hbPersistenceManager(YOURCONTEXTContext dbContext) : base(dbContext) { }
}
}

Please see this example: https://github.com/Breeze/northwind-demo
It is a full working example with a .NET Core 2.2 backend and Angular 8 front end, and includes TypeScript class generation so that the client-side TypeScript model matches the server-side C# model and database.
The example repo includes steps to create the entire app from scratch.

Related

fHow to have separate login/logout screen with Azure AD authentication

I have previously integrated with Okta and they have a sign in widget you can put into a page to log in with and call a controller to initiate the authentication/claims process. However, Azure AD does not seem to have anything like this so I wanted to use my razor page with our logo on it located in /pages/login/index to be the only folder for anonymous viewing. Basically I would like it to be the first screen anyone sees when they come to the site when not logged in and it will have a button which calls a controller post action to take you to Microsofts Azure AD Login screen. I have not seen any examples of this online so I am curious if this is even possible?
My current solution takes you straight to microsofts login:
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using Project.Models;
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//authentication pipeline
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
var RootPath = builder.Environment.ContentRootPath;
var WebPath = builder.Environment.WebRootPath;
var fileDirectory = Path.Combine(Directory.GetParent(RootPath).Parent.ToString(), "armsfiles");
IFileProvider physicalProvider = new PhysicalFileProvider(fileDirectory);
builder.Services.AddSingleton<IFileProvider>(physicalProvider);
//Not needed. We are not using this level of abstraction but may move towards it one day so possibly keep.
var connectionString = builder.Configuration.GetConnectionString("DBContext");
builder.Services.AddDbContext<DBContext>(options => options.UseSqlServer(connectionString));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
//We are making it so armsfiles are not accessible outside of arms so if we move to box or onedrive then the parameter may need to be removed.
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = physicalProvider,
RequestPath = "/files"
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
app.Run();
I was thinking I could add this:
// Add services to the container.
builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
});
But since there is no redirect for non logged in users that I can find I do not know if this option even makes a difference. So basically it is unclear how to setup the Program file to ensure not logged in users are redirected to this razor page (/login/index) and also what a controller for a sign in and sign out button would even look like! I am surprised I have struggled to find an example of this for ASP.NET Core 6.
**EDIT 11/22/2022
I have since got every component of my question solved except when a user goes to the site, if they are not authenticated I want to redirect them to "/login/" which hosts a page with a button that OnPost() does the following challenge:
public class IndexModel : PageModel
{
private string SignedOutRedirectUri;
public IndexModel(IConfiguration configuration)
{
SignedInRedirectUri = configuration["AzureAd:SignedInRedirectUri"];
}
public void OnGet()
{
}
public IActionResult OnPost()
{
return Challenge(
new AuthenticationProperties { RedirectUri = SignedInRedirectUri },
OpenIdConnectDefaults.AuthenticationScheme);
}
}
Is there a way to redirect users to a login page similar to how ASP.NET Core allows a redirect for their cookie options? ie,
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(cookieOptions =>
{
cookieOptions.Cookie.Name = "UserLoginCookie";
cookieOptions.LoginPath = "/Login/";
cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(30);
cookieOptions.SlidingExpiration = true;
});
Create HomePageRouteModelConvention.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace WebApplication3
{
public class HomePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
if (model.RelativePath == "/Pages/Index.cshtml")
{
var currentHomePage = model.Selectors.Single(s => s.AttributeRouteModel.Template == string.Empty);
model.Selectors.Remove(currentHomePage);
}
if (model.RelativePath == "/Pages/Login/Index.cshtml")
{
model.Selectors.Add(new SelectorModel()
{
AttributeRouteModel = new AttributeRouteModel
{
Template = string.Empty
}
});
}
}
}
}
Refer my startup.cs file.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
namespace WebApplication3
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI().AddRazorPagesOptions(options =>
{
options.Conventions.Add(new HomePageRouteModelConvention());
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.Run();
}
}
}
Others
Test Result:

Blazor IStringLocalizer injection to services

I am using IStringLocalizer approach to localize my Blazor app as discussed here.
Injecting the IStringLocalizer on razor pages works great. I also need this to localize some services - whether scoped or even singleton services.
Using constructor injection to inject my IStringLocalizer service into the service works. However, when users change the language via UI, the service (whether singleton or scoped) keeps the initial IStringLocalizer - i.e. the one with the original language used when starting the app, not the updated language selected by the user.
What is the suggested approach to retrieve the updated IStringLocalizer from code?
EDIT
To prevent more details, here is some piece of code.
First, I add a Resources folder and create there a default LocaleResources.resx (with public modifiers) and a LocaleResources.fr.resx file, which contain the key-value pairs for each language.
Supported cultures are defined in the appsettings.json file as
"Cultures": {
"en-US": "English",
"fr": "Français (Suisse)",
...
}
In startup, I register the Resources folder and the supported cultures :
public void ConfigureServices(IServiceCollection services {
...
services.AddLocalization(options => options.ResourcesPath = "Resources");
...
services.AddSingleton<MySingletonService>();
services.AddScoped<MyScopedService>();
}
// --- helper method to retrieve the Cultures from appsettings.json
protected RequestLocalizationOptions GetLocalizationOptions() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
var supportedCultures = cultures.Keys.ToArray();
var localizationOptions = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
return localizationOptions;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
app.UseRequestLocalization(GetLocalizationOptions());
...
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
I created an empty LocaleResources.razor control at the root of the project (this is a trick used to inject a single resource file to all components).
I included a routing controller to change language :
[Route("[controller]/[action]")]
public class CultureController : Controller {
public IActionResult SetCulture(string culture, string redirectUri) {
if (culture != null) {
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return LocalRedirect(redirectUri);
}
}
And the language UI switcher looks like this (I use SyncFusion control here, but it could be any lookup actually, that shouldn't really matter)
#inject NavigationManager NavigationManager
#inject IConfiguration Configuration
<SfComboBox TValue="string" TItem="Tuple<string, string>" Placeholder="Select language" DataSource="#Cultures"
#bind-Value="selectedCulture" CssClass="lan-switch" Width="80%">
<ComboBoxFieldSettings Text="Item2" Value="Item1"></ComboBoxFieldSettings>
</SfComboBox>
<style>
.lan-switch {
margin-left: 5%;
}
</style>
#code {
string _activeCulture = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
private string selectedCulture {
get => _activeCulture;
set {
_activeCulture = value;
SelectionChanged(value);
}
}
List<Tuple<string, string>> Cultures;
protected override void OnInitialized() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
Cultures = cultures.Select(p => Tuple.Create<string, string>(p.Key, p.Value)).ToList();
}
protected override void OnAfterRender(bool firstRender) {
if (firstRender && selectedCulture != AgendaSettings.SelectedLanguage) {
selectedCulture = AgendaSettings.SelectedLanguage;
}
}
private void SelectionChanged(string culture) {
if (string.IsNullOrWhiteSpace(culture)) {
return;
}
AgendaSettings.SelectedLanguage = culture;
var uri = new Uri(NavigationManager.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&" +
$"redirectUri={Uri.EscapeDataString(uri)}";
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
}
}
Finally, to the injection. I inject the IStringLocalizer to pages as follows and it works perfectly fine on razor controls:
#inject IStringLocalizer<LocaleResources> _loc
<h2>#_loc["hello world"]</h2>
Above, when I change language, the page displays the value in the corresponding resource file.
Now, to services: the MySingletonService and MyScopedService are registered at startup. They both have a constructor like
protected IStringLocalizer<LocaleResources> _loc;
public MySingletonService(IStringLocalizer<LocaleResources> loc) {
_loc = loc;
}
public void someMethod() {
Console.WriteLine(_loc["hello world"])
}
I run someMethod on a timer. Strangely, when I break on the above line, the result seems to oscillate : once it returns the default language's value, once the localized one...!
The answer to my question was: your code is correct!
The reason, I found out, is that I use a Scoped service that is started on the default App's start page:
protected async override Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
MyScopedService.StartTimer();
}
await base.OnAfterRenderAsync(firstRender);
}
When users change language, the whole page is refreshed and a new instance of the scoped service is created and timer started. As my service did not implement IDisposable, the timer was not actually stopped.
So 2 solutions here:
use singleton services
make servcie disposable and ensure tasks are cancelled when service is disposed of.

Resource translations no longer applied x time after installation

I've been tasked with making an update to an existing asp core mvc site. I had to add a filter to an existing report.
The site uses resource files for string translations.
After uploading the site to the server, the site is running, everything is fine. the site runs under a different port for test purposes than the live site.
The moment my colleague tests the site, the site shows with it's keys only.
When i check immediatly after, for me as well, no resource strings are shown, only the keys.
Restarting IIS doesn't help. The only thing that temporarily works, is copying the files again from my computer to the server. Then the translation strings work again. Untill they don't.
I have no clue as what could be the cause and i am looking for ideas to check.
The current things i want to confirm is, when it reverts to keys, if the .resources.dll is still there. Although the virus scanner logs don't show activity, and the production site which is on the same server doesn't expoerince this problem, sepite having the same .resources.dll.
If you think knowing the startup.cs might be helpfull, let me know, or if someone thinks 'if i could only know that', just give out a shout. All input is welcome. It's difficult for me now to know what info could be usefull and what not. Posting a link to the whole reporsitory isn't really an option for me. I hope you understand.
EDIT 2021/02/23
Added a redacted startup.cs
Personal progress, it's not the wrong language it's picking up rather then it's showing the resource keys after x time. The resource keys happen to be in english. It's as if the french resource dll gets deleted, but only infor the test version of the site.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
...
using DevExpress.AspNetCore;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
namespace ...
{
public class Startup
{
private const string Loginpath = "";
public static string LiveUrl { get; set; }
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
private RequestLocalizationOptions SiteLocalizationOptions { get; }
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
IList<CultureInfo> supportedCultures = new List<CultureInfo>
{
// new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
};
SiteLocalizationOptions = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture("fr-FR"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
//the third culture provider looks at the browsers preferenced culture. When the first visitor give english as prefered language, somehow parts of the site become english for subsequent all visitors.
//at some point there was an english site option, and artifacts of that remain throughout the code.
//clearing the providers should force the site to always use English.
SiteLocalizationOptions.RequestCultureProviders.Clear();
HostingEnvironment = env;
...
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
//Add localization and translations
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(options =>
{
options.SupportedCultures = SiteLocalizationOptions.SupportedCultures;
options.SupportedUICultures = SiteLocalizationOptions.SupportedUICultures;
options.DefaultRequestCulture = SiteLocalizationOptions.DefaultRequestCulture;
});
services.AddDevExpressControls();
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver())
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SiteResource));
})
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver())
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => options.LoginPath = new PathString(Loginpath));
...
//config
...
services.Configure<DatabaseSettings>(Configuration.GetSection("Database"));
services.Configure<HtmlPageSettings>(Configuration.GetSection("HtmlPages"));
services.Configure<ReportSettings>(Configuration.GetSection("Reporting"));
services.Configure<FilesSettings>(Configuration.GetSection("Files"));
services.Configure<AdminSettings>(Configuration.GetSection("Administrator"));
LatContext.ConnectionString = ...;
CroContext.ConnectionString = ...;
if (HostingEnvironment.IsDevelopment())
{
//repositories
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IStoredProcedures, StoredProcedures>();
//services
services.AddScoped<ILoginService, LoginService>();
}
else if (HostingEnvironment.IsEnvironment("Mock"))
{
//repositories
//No need to add repositories, since they are not used when the services are mocked
//services
services.AddScoped<ILoginService, MockLoginService>();
}
else //production
{
//repositories
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IStoredProcedures, StoredProcedures>();
//services
services.AddScoped<ILoginService, LoginService>();
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else if (env.IsEnvironment("Mock"))
{
}
else //production
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
//app.UseStaticFiles(new StaticFileOptions
//{
// FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "node_modules")),
// RequestPath = "/node_modules"
//});
app.UseCookiePolicy();
app.UseAuthentication();
app.UseRequestLocalization(SiteLocalizationOptions);
...
app.UseDevExpressControls();
app.UseMvc(mvcRoutes =>
{
...
});
}
}
}

How to create Web API that can be imported into Azure API Management Portal

so I have fiddled around a bit with Azure API Management Portal. I have followed the tutorial on how the import the conference api and managed to get it to work.
Then I created a WebApi app that uses swagger. My configuration is as follows:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
...
}
public void Configure(IApplicationBuilder app,
IServiceProvider services,
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Address Service API");
});
app.UseHttpsRedirection();
app.UseMvc();
}
If I run this and navigate to https://my-api/swagger, I can see the swagger UI and I can also see the specification when I click on the link on the swagger UI or visit the url https://my-api.azurewebsites.net/swagger/v1/swagger.json
So my problem is, I have no idea on how to actually import this into AAMP. I can publish it to a app service and it works from there, but if I try to import the url https://my-api.azurewebsites.net/swagger/v1/swagger.json into the AAMP, I get an error:
So I wait an hour and try again, only the be greeted by the same error and I think I am missing something because when I imported the conference api specification, it had a different url than mine, yet I cannot find anything or I am searching for the wrong things. Can anybody please give me a heads up here?
I have also tried searching for the sources of the conference API so I can deduct what I am doing wrong but I didn't have any luck on finding those.
Importing Swagger document into APIM is pretty straight forward by following this Azure document. There’s no issue when you import Swagger 1.2 documents. However, if you’re intending to import Swagger 2.0 ones, you might be facing these kind of issue
If you’re building an API app with .NET Framework 4.5+, using Swashbuckle library, it would be fine. However, if you’re building the app with ASP.NET Core, it does bring you a headache. Firstly, look at your Startup.cs file. The ConfigureService method looks like:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen();
services.ConfigureSwaggerDocument(
options =>
{
options.SingleApiVersion(new Info() { Version = "v1", Title = "Swagger UI" });
options.IgnoreObsoleteActions = true;
options.OperationFilter(new ApplyXmlActionComments(GetXmlPath(appEnv)));
});
services.ConfigureSwaggerSchema(
options =>
{
options.DescribeAllEnumsAsStrings = true;
options.IgnoreObsoleteProperties = true;
options.CustomSchemaIds(type => type.FriendlyId(true));
options.ModelFilter(new ApplyXmlTypeComments(GetXmlPath(appEnv)));
});
...
}
private static string GetXmlPath(IApplicationEnvironment appEnv)
{
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var assemblyName = assembly.GetName().Name;
var path = $#"{appEnv.ApplicationBasePath}\{assemblyName}.xml";
if (File.Exists(path))
{
return path;
}
var config = appEnv.Configuration;
var runtime = $"{appEnv.RuntimeFramework.Identifier.ToLower()}{appEnv.RuntimeFramework.Version.ToString().Replace(".", string.Empty)}";
path = $#"{appEnv.ApplicationBasePath}\..\..\artifacts\bin\{assemblyName}\{config}\{runtime}\{assemblyName}.xml";
return path;
}
In addition to this, the Configure method might look like:
public void Configure(IApplicationBuilder app)
{
...
app.UseSwaggerGen();
app.UseSwaggerUi();
...
}
Wen need to include two additional properties – host and schemes. Swagger specification clearly declares that both are NOT required. However, APIM DOES require both properties to be included in the swagger.json document.
So, how can we sort this out?
For your app in .NET 4.5+, just make sure that your SwaggerConfig.cs has activated those options with proper settings:
SwaggerDocsConfig.Schemes(new[] { “http”, “https” });
SwaggerDocsConfig.RootUrl(req => GetRootUrlFromAppConfig());
In your ASP.NET Core app, it might be tricky as you should implement the IDocumentFilter interface. Here’s a sample code:
public class SchemaDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Host = "localhost:44321";
swaggerDoc.BasePath = "/";
swaggerDoc.Schemes = new List<string>() { "https" };
}
}
And this SchemaDocumentFilter should be added into your ConfigureService method in Startup.cs:
public static void ConfigureServices(this IServiceCollection services)
{
...
services.AddSwaggerGen();
services.ConfigureSwaggerDocument(
options =>
{
options.SingleApiVersion(new Info() { Version = "v1", Title = "Swagger UI" });
options.IgnoreObsoleteActions = true;
options.OperationFilter(new ApplyXmlActionComments(GetXmlPath(appEnv)));
options.DocumentFilter<SchemaDocumentFilter>();
});
services.ConfigureSwaggerSchema(
options =>
{
options.DescribeAllEnumsAsStrings = true;
options.IgnoreObsoleteProperties = true;
options.CustomSchemaIds(type => type.FriendlyId(true));
options.ModelFilter(new ApplyXmlTypeComments(GetXmlPath(appEnv)));
});
...
}
Once you complete this, then import your swagger.json to APIM then it should work.
Reference:
Hope it helps.

Session has not been configured for this application or request error

I am very new to asp.net I recently I came across this exception:
System.InvalidOperationException
The details of the exception says:
Session has not been configured for this application or request.
Here is the code snippet where it happens:
[HttpPost]
public object Post([FromBody]loginCredentials value)
{
if (value.username.Equals("Admin")
&&
value.password.Equals("admin"))
{
HttpContext.Session.Set("userLogin", System.Text.UTF8Encoding.UTF8.GetBytes(value.username)); //This the line that throws the exception.
return new
{
account = new
{
email = value.username
}
};
}
throw new UnauthorizedAccessException("invalid credentials");
}
I have no idea why it's happening or what does this error actually mean.
Can someone please explain what might be causing this?
In your Startup.cs you might need to call
app.UseSession before app.UseMvc
app.UseSession();
app.UseMvc();
For this to work, you will also need to make sure the Microsoft.AspNetCore.Session nuget package is installed.
Update
You dont not need to use app.UseMvc(); in .NET Core 3.0 or higher
Following code worked out for me:
Configure Services :
public void ConfigureServices(IServiceCollection services)
{
//In-Memory
services.AddDistributedMemoryCache();
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(1);
});
// Add framework services.
services.AddMvc();
}
Configure the HTTP Request Pipeline:
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
I was getting the exact same error in my .NET Core 3.0 Razor Pages application. Since, I'm not using app.UseMvc() the proposed answer could not work for me.
So, for anyone landing here having the same problem in .NET Core 3.0, here's what I did inside Configure to get it to work:
app.UseSession(); // use this before .UseEndpoints
app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); });
For .NET 6:
Go to your Program.cs first (to add the code there)
I inserted this at the last part of the builder:
builder.Services.AddSession();
I added this after app.UseAuthorization();
app.UseSession();
Final Output:
Note: I don't know if this is really the "right lines" to place the code, but this is what worked for me. (I'm still a beginner in ASP.NET as well)
If you use .NET 6 add these below into your Program.cs (may not work in older versions):
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
app.UseSession();
Be sure if builder and app are already declared. If it is not, add these below before the ones I said before:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
for .net core 5
Below lines should be added in startup.cs
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
app.UseSession(); should be added before
app.UseAuthentication();
app.UseAuthorization();
Below codes can be used for set the value and reading the value
this.httpContext.Session.SetString(SessionKeyClientId, clientID);
clientID = this.httpContext.Session.GetString(SessionKeyClientId);
If you got this issue and your startup was correctly setup: This error appeared for me when I forgot to await and async method that used IHttpContextAccessor that was injected. By adding await to the call the issue was resolved.
HttpContext.Session.Add("name", "value");
OR
HttpContext.Session["username"]="Value";

Resources