Using DisplayModeProvider to choose between views for "Desktop", "Tablet" and "Phone" in a MVC5 web application. It is my understanding that this class selects the correct provider in order and uses the first provider that returns True. However, when I step through the code, I find there is a repeated cycle through the code (it goes through multiple times, sometimes over 10 cycles) before deciding on the proper mode. I'm using WURFL Cloud for device detection. Lastly, I've started caching WURFL results in a Session variable. Thinking there must be something wrong with my code and/or logic. It's in VB.net since it's an evolution of a legacy project. The first block of code is in Application_Start in global.asax. Before it was in a separate class, but moved it to global.asax in an attempt to solve this problem.
DisplayModeProvider.Instance.Modes.Clear()
DisplayModeProvider.Instance.Modes.Add(New DefaultDisplayMode("Phone") With {.ContextCondition = Function(c) c.Request.IsPhone})
DisplayModeProvider.Instance.Modes.Add(New DefaultDisplayMode("Tablet") With {.ContextCondition = Function(c) c.Request.IsTablet})
DisplayModeProvider.Instance.Modes.Add(New DefaultDisplayMode("") With {.ContextCondition = Function(c) c.Request.IsDesktop})
My understanding is the function would check for each context condition and stop at the first one that is true. However, as mentioned above the code repeatedly executes even though one of the functions returns True.Here are the extension methods I'm using. They reside in a module. The error handling code was added after a "perceived" outage of the WURFL cloud. Each is decorated with the following: System.Runtime.CompilerServices.Extension
Public Function IsPhone(request As HttpRequestBase) As Boolean
Dim ans As Boolean
Try
If Not HttpContext.Current.Session("IsPhone") Is Nothing Then
ans = HttpContext.Current.Session("IsPhone")
Else
wsm = New WURFLServiceModel(New HttpContextWrapper(HttpContext.Current))
ans = wsm.IsPhone
HttpContext.Current.Session("IsPhone") = ans
End If
Catch ex As Exception
...
End Try
Return ans
End Function
Public Function IsTablet(request As HttpRequestBase) As Boolean
Dim ans As Boolean
Try
If Not HttpContext.Current.Session("IsTablet") Is Nothing Then
ans = HttpContext.Current.Session("IsTablet")
Else
wsm = New WURFLServiceModel(New HttpContextWrapper(HttpContext.Current))
ans = wsm.IsTablet
HttpContext.Current.Session("IsTablet") = ans
End If
Catch ex As Exception
...
End Try
Return ans
End Function
Public Function IsDesktop(request As HttpRequestBase) As Boolean
Return True
End Function
Here is the code for the WURFLServiceModel
Imports ScientiaMobile.WurflCloud.Device
Public Class WURFLServiceModel
Private mIsIOS As Boolean
Private mIsTablet As Boolean
Private mIsPhone As Boolean
Private mResponse As String
Private mErrors As Dictionary(Of String, String)
Private api_Key As String = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
Public Sub New(ByVal request As HttpContextBase)
GetDataByRequest(request)
End Sub
Public Sub GetDataByRequest(context As HttpContextBase)
Dim config = New DefaultCloudClientConfig(api_Key)
Dim manager = New CloudClientManager(config)
Dim info = manager.GetDeviceInfo(context)
mIsIOS = info.Capabilities("is_ios")
mIsPhone = info.Capabilities("is_smartphone")
mIsTablet = info.Capabilities("is_tablet")
mBrandName = info.Capabilities("brand_name")
mModelName = info.Capabilities("model_name")
mErrors = info.Errors
mResponse = info.ResponseOrigin
End Sub
Public ReadOnly Property IsDesktop As Boolean
Get
Return True
End Get
End Property
Public ReadOnly Property IsIOS As Boolean
Get
Return mIsIOS
End Get
End Property
Public ReadOnly Property IsTablet As Boolean
Get
Return mIsTablet
End Get
End Property
Public ReadOnly Property IsPhone As Boolean
Get
Return mIsPhone
End Get
End Property
Although the application runs without error, I can't believe the cycling through this routine should be happening. Would like to clear it up, if possible. What am I doing wrong? Many thanks in advance!
As I see it, the issue has more to do with the internal implementation of MVC display modes than the WURFL API. The code bound to the display mode delegate is called back by ASP.NET MVC for each request to render a view, including partial views. This obviously results in multiple calls being made to the WURFL API. In addition, the WURFL Cloud API takes a while to respond because it has to make a HTTP request to the cloud, parse a cookie, and figure out details. The WURFL Cloud is clearly slower than the on-premise WURFL API which uses a direct access memory cache to pick up details of the user agent. I have used WURFL and MVC in a number of web sites and just went through this. For most of such sites I managed to get an on-premise license. As for the cloud, some per-request internal caching perhaps within your WURFLServiceModel class would be helpful so that you end up making a single cloud request for each rendering of the view. I don't particularly like the use of Session, but yes that could just be me. Session is still an acceptable way of do the "internal caching" I was suggesting above.
Luca Passani, ScientiaMobile CTO here. I instructed the support and engineering teams to reach out to you offline and work with you to get to the end of the issue you are experiencing. For the SO admins. We will report a summary here when the issue is identified and resolved. Thanks.
Related
I'm rather new to Blazor, but I am currently trying to get access to some classes from within a class library that I've created and deployed as a Nuget package. As background, the Nuget package is an Api library, which allows me to talk to a webservice (I don't know if this is relevant or not). However, every time I go to the page where I'm testing, the page never loads and instead I left looking at the browser loading circle until I navigate away or close the application. During my testing here, it seems like it's the #inject call of my interface into the Blazor component which is causing the issue as when I remove it and try to load the page normally, the page does so.
So to demonstrate what I have setup, here is where I've added the Singletons to the DI:
builder.Services.AddSingleton<IApiConfigHelper, ApiConfigHelper>();
builder.Services.AddSingleton<IApiHelper, ApiHelper>();
builder.Services.AddSingleton<ISystemEndpoint, SystemEndpoint>();
Then on the blazor page, I have the following declarations at the top of my page:
#using Library.Endpoints
#using Library.Models
#page "/"
#inject ISystemEndpoint _systemEndpoint
Now I am leaning towards is this something to do with the Nuget package and using it with DI. I have tested the library away from this project (In a console application) and can confirm it's working as it should.
I have also created a local class library as a test to, to see if I could inject a data access class into the page and I can confirm that this works without an issue, which suggests to me that DI is working, just not with my Nuget package.
I did have a look into CORS, given that the Nuget package is accessing an external domain, and setup the following simple CORS policy in the app:
builder.Services.AddCors(policy =>
{
policy.AddPolicy("OpenCorsPolicy", opt =>
opt.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
Which is added to the app after the AddRouting call like so:
app.UseCors("OpenCorsPolicy");
However again, this wasn't the solution so if anyone is able to point me in the right direction with where I may be going wrong with this or offer any advice, I would be most grateful.
EDIT 1 - Provides details #mason queried
Regarding SystemEndpoint, the constructor is being injected with 2 things, as below:
public SystemEndpoint(IApiHelper apiHelper, IOptions<UriConfigModel> uriOptions)
{
_apiHelper = apiHelper;
_uriOptions = uriOptions.Value;
}
My Nuget Library is dependant on the following:
Azure.Identity
Azure.Security.KeyVault.Secrets
Microsoft.AspNet.WebApi.Client
Microsoft.Extensisons.Options.ConfigurationExtensions
EDIT 2 - Doing some further testing with this I have added a simple Endpoint class to my Nuget library, which returns a string with a basic message, as well as returning the values of the 2 UriConfig properties as below. I added this test to 1) sanity check that my DI was working correctly, and 2) check the values that are being assigned from appsettings to my UriConfig Object.
public class TestEndpoint : ITestEndpoint
{
private readonly IOptions<UriConfigModel> _uriConfig;
public TestEndpoint(IOptions<UriConfigModel> uriConfig)
{
_uriConfig = uriConfig;
}
public string TestMethod()
{
return $"You have successfully called the test method\n\n{_uriConfig.Value.Release} / {_uriConfig.Value.Version}";
}
}
However when adding in the dependency of IApiHelper into the Ctor, the method then breaks and fails to load the page. Looking into ApiHeloer, the Ctor has a dependency being injected into it of IApiConfigHelper. Looking at the implementation, the Ctor of ApiConfigHelper is setting up the values and parameters of the HttpClient that should make the REST calls to the external Api.
Now I believe what is breaking the code at this point is a call I'm making to Azure Key Vault, via REST, to pull out the secret values to connect to the Api. The call to KeyVault is being orchestrated via the following method, making use of the Azure.Security.KeyVault.Secrets Nuget Package, however I assume that at the heart of it, it's making a REST call to Azure on my behalf:
private async Task<KeyVaultSecret> GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = await _secretClient.GetSecretAsync(secretName);
return result.Value;
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
So that's where I stand with this at the moment. I still believe it could be down to CORS, as it seems to be falling over when making a call to an external service / domain, but I still can say 100%. As a closing thought, could it be something as simple as when I call call the above method, it's not being awaited????
So after persisting with this it seems like the reason it was failing was down to "awaiting" the call to Azure KeyVault, which was happening indirectly via the constructor of ApiConfigHelper. The resulting method for getting KeyVault value is now:
private KeyVaultSecret GetKeyVaultValue(string secretName = "")
{
try
{
if (_secretClient is not null)
{
var result = _secretClient.GetSecret(secretName);
if (result is not null)
{
return result.Value;
}
}
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (Azure.RequestFailedException rfe)
{
Console.WriteLine(rfe.Message);
}
return new(secretName, "");
}
I am now able to successfully make calls to my library and return values from the Api it interacts with.
I can also confirm that this IS NOT a CORS issue. Once I saw that removing the await was working, I then removed the CORS policy declarations from the service and the app in my Blazor's start-up code and everything continued to work without an issue.
As a final note, I must stress that this is only seems an issue when using the library with Blazor (possibly webApi projects) as I am able to use the library, awaiting the Azure call just fine in a console application.
I am using Identity Server 4 for authentication for both a website and WPF application. On the website, I want users to have the ability to check a Remember Me box when signing in, but I don't want that for the WPF application. I have the logic to disable that checkbox on the front end, but am having trouble in my controller. I have this function
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
LoginViewModel _vm;
_vm = await BuildLoginViewModelAsync(returnUrl);
//if(Client_id == "wpf") <- this is what I need help with
//{
// _vm.AllowRememberMe = false;
//}
return View(_vm);
}
This controller contains
private readonly IIdentityServerInteractionService mInteraction;
private readonly IClientStore mClientStore;
private readonly IAuthenticationSchemeProvider mSchemeProvider;
private readonly IEventService mEvents;
Any help would be appreciated
You can get the client id from the AuthorizationRequest returned from the IIdentityServerInteractionService as follows using your code snipet:
var context = await mInteraction.GetAuthorizationContextAsync(returnUrl);
_vm.AllowedRememberMe = context.ClientId != "wpf";
However, you would be better off placing this logic in your BuildLoginViewModelAsync method where the view model is constructed rather than setting the property after construction.
I don't believe the client_id is directly available from any IS4 constructs in the Login method. However, depending on your OIDC flow, it's likely that your client_id was passed as part of the "returnUrl" parameter. Look at your return URL and see if it's there.
For example, I have a spa website connecting to IS4 that shows a returnURL of:
https://localhost:8080/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DspaClient%26redirect_uri%3Dhttps...(long url continues)
You can see that it contains the "client_id" parameter with a value of "spaClient". Simply parse the returnUrl using your code of choice (e.g. RegEx) and extract the client_id from there. I don't have any WPF experience, so it may behave differently and not pass this parameter.
I've set up a Web API project using Ninject, and I've used the fix detailed here for getting it to work with the latest version of the Web API. Everything is working fine, but I'm now trying to write some tests.
I'm using in-memory hosting to run the project for the tests, as detailed here, as I have a DelegatingHandler that performs authentication and then sets a property on the request message that is used by all the Api Controllers.
So, I've got a base class for my tests, and have a SetUp method where I set up the HttpServer and configuration, which I've pretty much taken from my working Ninject code:
[SetUp]
public void Setup()
{
bootstrapper = new Bootstrapper();
DynamicModuleUtility.RegisterModule(
typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(
typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("Login",
"api/auth/token",
new { controller = "Users", action = "Login" });
config.IncludeErrorDetailPolicy =
IncludeErrorDetailPolicy.Always;
config.DependencyResolver =
new NinjectResolver(CreateKernel());
config.MessageHandlers.Add(
new AuthenticationHandler(CreateUserManager()));
Server = new HttpServer(config);
}
This is how I create the MoqMockingKernel:
private static IKernel CreateKernel()
{
var kernel = new MoqMockingKernel();
kernel.Bind<Func<IKernel>>()
.ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>()
.To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver =
new NinjectResolver(kernel);
return kernel;
}
And this is how I register the objects to use:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUserManager>().ToMock();
kernel.Bind<UsersController>().ToSelf();
}
While I'm not testing the Controller per se, I do want a proper instance of it to be called, which is why I'm binding it ToSelf. I must admit that I am assuming that this is correct. This is an example of a test:
public void UserCannotLogin()
{
System.Net.Http.HttpClient client =
new System.Net.Http.HttpClient(Server);
string json = string.Format(
"{{ \"Username\": \"{0}\", \"Password\": \"{1}\" }}",
"wrong", "wrong");
HttpRequestMessage request =
CreateRequest(#"api/auth/token", json, HttpMethod.Get);
Action action = () => client.SendAsync(request);
using (var response = client.SendAsync(request).Result)
{
response.StatusCode.Should()
.Be(HttpStatusCode.Unauthorized);
}
}
I'm basically getting a 404 error. When I debug it, it does go to my DelegatingHandler, but it doesn't go to my controller.
I get the feeling that I'm fundamentally missing a point here, and it may not even be possible to do what I'm trying to do, but if anyone has any suggestions for either how to do this, or a different way to achieve the same thing, I'm all ears.
Update I think that it's because the default behaviour of the MockingKernel is to provide a Mock unless told otherwise, so it is returning a Mock of IHttpControllerSelector. I've set up a couple of default ones now:
kernel.Bind<IHttpControllerSelector>()
.To<DefaultHttpControllerSelector>();
kernel.Bind<IContentNegotiator>()
.To<DefaultContentNegotiator>();
It's still not working, I think because there are no formatters specified. I'll try that tomorrow and see if that gets me there.
Ok, I think that I was correct when I said that I was fundamentally missing a point here, but I'll answer this in case it helps someone else avoid the same mistake!
The Ninject MockingKernel is, I think, primarily about auto-mocking, so where you have a lot of interfaces you don't care about how they are set up in your test, you can ignore them in your tests and they will be automatically created for you.
In the case of the Web API, this is most definitely not the case, as you don't want the controller selector class to be auto mocked, otherwise you won't end up calling your controllers.
So, the solution I've come up with is to stick with using a standard Ninject Kernel, and then bind your interface to a constant Mock object:
kernel.Bind<IUserManager>().ToConstant(CreateUserManager());
private IUserManager CreateUserManager()
{
Mock<IUserManager> userManager = new Mock<IUserManager>();
// Set up the methods you want mocked
return userManager.Object;
}
Doing this, I've been able to successfully write tests that use an HttpClient to call an in-memory HttpServer that successfully call my DelegatingHandler and then end up at my controllers.
I have an application written in ASP.NET MVC 2 that stores the date and time of a user's scheduled event in an MSSQL database. I need the application to send an email alert to the user 72 hours before the stored event occurs without any manual intervention.
What is the best way to implement this parallel process (I already have the email code)?
Assuming you have the events stored in a database, I would create a Windows Service which ran independently of the asp.net application, which regularly polled the database looking for work to be done.
If you have control over the server then I would probably go with the Windows Service (like The Evil Greebo said).
But assuming you don't, I would create a class, perhaps similar to the one below, which you can use/inherit from. Instantiate this class during the "Application_start" event in global.asax and save a copy of it in the cache. Put your logic in the "ExecuteProcess" method. Your logic would probably look something like (pseudo code):
while(true)
check for events that need to have notifications sent
send any needed notifications
wait x seconds and repeate
loop
Make sure you have a field in your MSSQL database table for tracking whether a notification has been sent.
Basic base class:
Imports System.Threading
Public MustInherit Class LongRunningProcess
Public ReadOnly Property Running() As Boolean
Get
Return _Running
End Get
End Property
Public ReadOnly Property Success() As Boolean
Get
Return _Success
End Get
End Property
Public ReadOnly Property Exception() As Exception
Get
Return _Exception
End Get
End Property
Public ReadOnly Property StartTime() As DateTime
Get
Return _StartTime
End Get
End Property
Public ReadOnly Property EndTime() As DateTime
Get
Return _EndTime
End Get
End Property
Public ReadOnly Property Args() As Object
Get
Return _Args
End Get
End Property
Protected _Running As Boolean = False
Protected _Success As Boolean = False
Protected _Exception As Exception = Nothing
Protected _StartTime As DateTime = DateTime.MinValue
Protected _EndTime As DateTime = DateTime.MinValue
Protected _Args() As Object = Nothing
Protected WithEvents _Thread As Thread
Private _locker As New Object()
Public Sub Execute(ByVal Arguments As Object)
SyncLock (_locker)
'if the process is not running, then...'
If Not _Running Then
'Set running to true'
_Running = True
'Set start time to now'
_StartTime = DateTime.Now
'set arguments'
_Args = Arguments
'Prepare to process in a new thread'
_Thread = New Thread(New ThreadStart(AddressOf ExecuteProcess))
'Start the thread'
_Thread.Start()
End If
End SyncLock
End Sub
Protected MustOverride Sub ExecuteProcess()
End Class
The Args property gives you access from within "ExecuteProcess" to any arguments/variables you might need.
I've implemented a solution which involves Rhino.Security to manage user/roles/permissions.
Since I want to check if a user is authorized to access a controller action, I've implemented a custom action filter:
public class AuthorizationAttribute : ActionFilterAttribute
{
CustomPrincipal currentPrincipal = (CustomPrincipal)filterContext.HttpContext.User;
var actionName = filterContext.ActionDescriptor.ActionName;
var controllerName = filterContext.Controller.GetType().Name;
var operation = string.Format("/{0}/{1}", controllerName, actionName);
if (!securityService.CheckAuthorizationOnOperation(currentPrincipal.Code, operation))
{
filterContext.Controller.TempData["ErrorMessage"] = string.Format("You are not authorized to perform operation: {0}", operation);
filterContext.Result = new HttpUnauthorizedResult();
}
}
CheckAuthorizationOnOperation calls Rhino.Security to check if the user is allowed to the operation specified:
AuthorizationService.IsAllowed(user, operation);
Everything works properly but I've noticed that the second-level cache is never hit when the query called by IsAllowed is executed.
I've investigated and I've seen that the framework (Rhino.Security) uses a DetachedCriteria. These are the 2 procedures called:
public Permission[] GetGlobalPermissionsFor(IUser user, string operationName)
{
string[] operationNames = Strings.GetHierarchicalOperationNames(operationName);
DetachedCriteria criteria = DetachedCriteria.For<Permission>()
.Add(Expression.Eq("User", user)
|| Subqueries.PropertyIn("UsersGroup.Id",
SecurityCriterions.AllGroups(user).SetProjection(Projections.Id())))
.Add(Expression.IsNull("EntitiesGroup"))
.Add(Expression.IsNull("EntitySecurityKey"))
.CreateAlias("Operation", "op")
.Add(Expression.In("op.Name", operationNames));
return FindResults(criteria);
}
private Permission[] FindResults(DetachedCriteria criteria)
{
ICollection<Permission> permissions = criteria.GetExecutableCriteria(session)
.AddOrder(Order.Desc("Level"))
.AddOrder(Order.Asc("Allow"))
.SetCacheable(true)
.List<Permission>();
return permissions.ToArray();
}
As you can see FindResults uses SetCacheable.
Everytime I refresh a page my action filter executes the procedures and the query is executed again, ignoring the cache (second-level).
Since I use extensively the cache and all the other calls work properly I would like to understand why this one doesn't work as expected.
Doing some research I've noticed that the second-level cache is used only if I call the function twice:
SecurityService.CheckAuthorizationOnOperation(currentPrincipal.Code, "/Users/Edit");
SecurityService.CheckAuthorizationOnOperation(currentPrincipal.Code, "/Users/Edit");
It seems that the cache for this particular situation only works if I am using the same session (nHibernate).
Is there anybody who can try to help me to figure out what's happening?
UPDATE:
Make sure you are doing the work inside a transaction
Verify that the Permission entity is cached too
There's an issue with this framework.
I opened a question on Google Groups, everyone knows about it, but it seems that the framework has been forgotten.