I'm trying to implement IChartStorageHandler via .Net4.5/MVC4 application to use on Azure based on http://goo.gl/WAapl
I couldn't be able to hit none of the breakpoints that I set in the class below. So my question is, "is there any trick that I can use to force it"? Thank you!
You may find the details about the parameters at http://msdn.microsoft.com/en-us/library/dd456629.aspx
namespace MvcWebRole1.Codes
{
public class ChartImageHandler : IChartStorageHandler
{
public ChartImageHandler()
{
throw new NotImplementedException();
}
#region IChartStorageHandler Members
public void Delete(string key)
{
throw new NotImplementedException();
}
public bool Exists(string key)
{
throw new NotImplementedException();
}
public byte[] Load(string key)
{
throw new NotImplementedException();
}
public void Save(string key, byte[] data)
{
throw new NotImplementedException();
}
#endregion
}
}
web.config part #1
<appSettings>
<add key="ChartImageHandler" value="handler=MvcWebRole1.Codes.ChartImageHandler, MvcWebRole1; webDevServerUseConfigSettings=false;" />
</appSettings>
web.config part #2
<system.webServer>
<handlers>
<remove name="ChartImageHandler"/>
<add name="ChartImageHandler" path="ChartImg.axd" verb="GET,HEAD,POST" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode" />
</handlers>
</system.webServer>
controller
public ActionResult ChartImage()
{
// get data
string path = HttpContext.Server.MapPath("~/App_Data/Test.csv");
IEnumerable<Bar> data = BarRepository.Get(path);
// generate chart
byte[] chartResult = data.Generator();
// return the chart
return File(chartResult, "image/png");
}
public static class ChartOperations
{
private static Chart _chart1;
public static byte[] Generator(this IEnumerable<Bar> data)
{
// initial variable tasks
_chart1 = new Chart() {DataSource = data.ToList()};
// generate the chart
DoTheHardWork();
// save chart to memory string
var image = new MemoryStream();
_chart1.SaveImage(image);
var result = image.GetBuffer();
return result;
}
}
Can you check if you have any entries for ChartImageHandler under system.web/httpHandlers? If yes, then please remove it.
also, it might not relate to this but in you web.config part #1, shouldn't you mention storage=file; as well so as to make it look like :
<add key="ChartImageHandler" value="storage=file;handler=MvcWebRole1.Codes.ChartImageHandler, MvcWebRole1; webDevServerUseConfigSettings=false;" />
This might be a stupid question to ask but when you are trying to hit a break point in your handler are you actually opening a page that includes ASP.NET Charts? The Chart handler will be hit only when charts are being loaded.
Perhaps you could launch the debugger as soon as your class is created?
public ChartImageHandler()
{
System.Diagnostics.Debugger.Launch();
//throw new NotImplementedException();
}
More at http://msdn.microsoft.com/en-us/library/system.diagnostics.debugger.launch.aspx
Related
When an error occurs on my ASP.NET MVC Server (running on IIS), the server currently serves a static page. This is configured in the httpErrors element in my web.config, like so:
<httpErrors errorMode="Custom" existingResponse="Replace">
<error statusCode="404" path="404.htm" responseMode="File" />
<error statusCode="500" path="500.htm" responseMode="File" />
</httpErrors>
When inspecting the response from the server, I see a cache-control: private response header. This is good, though I want to control how long this page is cached. How can I add max-age=x to this cache-control header?
If I understand your problem statement correctly your main goal was to have control over max-age, rather than fancy <customErrors> setup. It seems logical to try and control the header from an Action Filter.
In web.config
I've got this system.web setup:
<system.web>
<compilation debug="true" targetFramework="4.6.1"/> <!-- framework version for reference -->
<httpRuntime targetFramework="4.6.1"/>
<customErrors mode="On">
</customErrors> <!-- I didn't try adding custom pages here, but this blog seem to have a solution: https://benfoster.io/blog/aspnet-mvc-custom-error-pages -->
</system.web>
In MaxAgeFilter.cs
public class MaxAgeFilter : ActionFilterAttribute, IResultFilter, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = (int)HttpStatusCode.InternalServerError;
if (filterContext.Exception is HttpException)
{
statusCode = (filterContext.Exception as HttpException).GetHttpCode();
}
else if (filterContext.Exception is UnauthorizedAccessException)
{
statusCode = (int)HttpStatusCode.Forbidden;
}
var result = CreateActionResult(filterContext, statusCode);
filterContext.Result = result;
// Prepare the response code.
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromSeconds(10)); // this requires a lot of extra plumbing which I suspect is necessary because if you were to rely on default error response - the cache will get overriden, see original SO answer: https://stackoverflow.com/questions/8144695/asp-net-mvc-custom-handleerror-filter-specify-view-based-on-exception-type
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromMinutes(10)); // this is easy - you just pass it to the current cache and magic works
base.OnResultExecuted(filterContext);
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
var viewName = SelectFirstView(ctx,
"~/Views/Shared/Error.cshtml",
"~/Views/Shared/Error.cshtml",
statusCodeName,
"Error");
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
protected string SelectFirstView(ControllerContext ctx, params string[] viewNames)
{
return viewNames.First(view => ViewExists(ctx, view));
}
protected bool ViewExists(ControllerContext ctx, string name)
{
var result = ViewEngines.Engines.FindView(ctx, name, null);
return result.View != null;
}
}
as you see, handling an exception basically requires rebuilding the whole Response. For this I pretty much took the code from this SO answer here
Finally, you decide whether you want this attribute on your controllers, actions or set up globally:
App_Start/FilterConfig.cs
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MaxAgeFilter());
}
}
If you're using custom error pages, you can set headers within the view. That way you set different cache lengths depending on which error page is being displayed, as you said.
#{
TimeSpan duration = new TimeSpan(0, 30, 0);
Response.Cache.SetMaxAge(duration);
}
I tried a lot of ways and it appear to work only when I set custom error page for 404 error.
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-Control" value="max-age:12300" />
</customHeaders>
</httpProtocol>
</system.webServer>
I have an owin culture middle ware running very nice.
It just changes the culture according to the url. This works in 4.5.* perfectly. Now when the runtiome is changed to 4.6.1, the culture isn't preserved anymore and as a result it just doesn't work.
I can reproduce it in a very simple solution which only has this middleware simulating the culture change
public class CultureMiddleware : OwinMiddleware
{
public CultureMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
var culture = new CultureInfo("es-ES");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
await Next.Invoke(context);
}
}
I attach the middleware to the pipeline it gets execute but when I'm calling an action the controller doesn't have the culture (like it had in .net 4.5.1)
I already posted here but the support is really slow. One Answer every two weeks and then it seems like they haven't tried what they write :-(
https://connect.microsoft.com/VisualStudio/feedback/details/2455357
I got a response from microsoft which works for me.
you can try set the following element in your web.config file. This
element has to be child to the <appSettings> element.
<add key="appContext.SetSwitch:Switch.System.Globalization.NoAsyncCurrentCulture" value="true" />
I also tried to fix it with OwinMiddleware but failed.
My solution was to create an ActionFilterAttribute and register this at startup:
public partial class Startup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
GlobalFilters.Filters.Add(new CultureCookieFilter());
base.Configuration(app);
}
}
public class CultureCookieFilter : ActionFilterAttribute
{
private const string CULTURE_KEY = "X-Culture";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.Cookies[CULTURE_KEY] != null)
{
var langCookie = filterContext.HttpContext.Request.Cookies[CULTURE_KEY];
if (langCookie != null)
{
var lang = langCookie.Value;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(lang);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
}
}
base.OnActionExecuting(filterContext);
}
}
I have an older database schema that I cannot change. It has a single user table with an integer field to designate user level where 1 is standard user and 5 is administrator. I'm writing an MVC front end and I want to use ASP.NET Identity. I've figured out everything else from research and the boilerplate code. I can't seem to figure out how to create a custom roles system. I realize it has something to do with implementing a role manager and role store. That's fine but how do I then connect that with MVC to get the AuthorizeAttribute to acknowledge my manager?
I apologize if this is obvious but I have done my research and I'm having trouble nailing it down.
From your question, I'm assuming you already figured out how to create your role manager and you are only missing the config to actually use it. If my assumptions are wrong, let me know and I will add explanation on how to create the CustomRoleManager.
Web.config
<configuration>
<system.web>
<roleManager enabled="true" defaultProvider="CustomRoleProvider">
<providers>
<clear/>
<add name="CustomRoleProvider"
type="MyNamespace.CustomRoleProvider,
MyNamespace, Version=1.0.0.0, Culture=neutral"
connectionStringName="MyConnectionString"
enablePasswordRetrieval="false"
enablePasswordReset="false"
requiresQuestionAndAnswer="false"
writeExceptionsToEventLog="false" />
</providers>
</roleManager>
</system.web>
</configuration>
Here is the RoleProvider that I used, if anyone has the same trivial requirements. If you know of any reason why this implementation is not secure, please let me know. I used #Pluc's answer in my Web.Config to connect this provider to my application. It worked wonderfully.
public class AppRole : IRole<int>
{
public AppRole(int a_id, string a_name)
{
Id = a_id;
Name = a_name;
}
public int Id { get; private set; }
public string Name { get; set; }
}
public class AppRoleProvider : RoleProvider
{
private readonly IServiceLocator _container = UnityConfig.GetServiceLocator();
private ITrainingRepository _repository; // Thin wrapper around my DbContext
private AppRole[] _roles = new[]
{
new AppRole(0, "User"),
new AppRole(5, "Admin"),
};
public AppRoleProvider()
{
ApplicationName = "TrainingCenter";
_repository = _container.GetInstance<ITrainingRepository>();
}
public override string ApplicationName { get; set; }
public override bool IsUserInRole(string username, string roleName)
{
var user = _repository.GetUserByUserName(username);
if (user == null)
return false;
var role = _roles.FirstOrDefault(i => i.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase));
if (role == null)
return false;
if (user.UserLevel >= role.Id)
return true;
return false;
}
public override string[] GetRolesForUser(string username)
{
var user = _repository.GetUserByUserName(username);
if (user == null)
return new string[] {};
return _roles.Where(i => i.Id <= user.UserLevel).Select(i => i.Name).ToArray();
}
public override void CreateRole(string roleName)
{
// Does not create.
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
// Does not delete.
return false;
}
public override bool RoleExists(string roleName)
{
return _roles.Any(i => i.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase));
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
// Does not add user to role.
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
// Does not remove users from roles.
}
public override string[] GetUsersInRole(string roleName)
{
// Does not get users in role.
return new string[] {};
}
public override string[] GetAllRoles()
{
return _roles.Select(i => i.Name).ToArray();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
// Does not find users in role.
return new string[] { };
}
}
I can't seem to get a custom VirtualPathProvider working in asp.net MVC 5.
The FileExists method returns true but then the GetFile method isn't called.
I believe this is because IIS takes over the request and does not let .NET handle it.
I have tried setting RAMMFAR and creating a custom handler, as in this solution https://stackoverflow.com/a/12151501/801189 but still no luck. I get a error 404.
My Custom Provider:
public class DbPathProvider : VirtualPathProvider
{
public DbPathProvider() : base()
{
}
private static bool IsContentPath(string virtualPath)
{
var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/CMS/", StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return IsContentPath(virtualPath) || base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath)
{
return IsContentPath(virtualPath) ? new DbVirtualFile(virtualPath) : base.GetFile(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
return null;
}
public override String GetFileHash(String virtualPath, IEnumerable virtualPathDependencies)
{
return Guid.NewGuid().ToString();
}
}
My Custom Virtual File:
public class DbVirtualFile : VirtualFile
{
public DbVirtualFile(string path): base(path)
{
}
public override System.IO.Stream Open()
{
string testPage = "This is a test!";
return new System.IO.MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(testPage));
}
}
web.config handler I have tried to use, without success. It currently gives error 500 :
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<remove name="FormsAuthenticationModule" />
</modules>
<handlers>
<add name="ApiURIs-ISAPI-Integrated-4.0"
path="/CMS/*"
verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="runtimeVersionv4.0" />
</handlers>
If I try to navigate to site.com/CMS/Home/Index, the FileExists method is called but strangely, the virtualPath parameter recieves only ~/CMS/Home.
Adding breakpoints, it seems that for the url site.com/CMS/Home/Index, the FileExists method keeps getting repeatedly called. This may be causing an infinite recursion, giving the internal server error.
It was actually nothing to do with IIS, and in fact confusion on the order of events. It seems I didn't understand that a routed action method must return a view, that the VirtualPathProvider will try to resolve, rather than going to the VirtualPathProvider directly.
I create a simple controller called ContentPagesController with a single GetPage action:
public class ContentPagesController : Controller
{
[HttpGet]
public ActionResult GetPage(string pageName)
{
return View(pageName);
}
}
I then set up my route to serve virtual pages:
routes.MapRoute(
name: "ContentPageRoute",
url: "CMS/{*pageName}",
defaults: new { controller = "ContentPages", action = "GetPage" },
constraints: new { controller = "ContentPages", action = "GetPage" }
);
I register my custom VirtualPathProvider before I register my routes, in globals.asax.cs.
Now suppose I have a page in my database with the relative url /CMS/Home/AboutUs. The pageName parameter will have value Home/AboutUs and the return View() call will instruct the VirtualPathProvider to look for variations of the file ~/Views/ContentPages/Home/AboutUs.cshtml.
A few of the variations it will be look for include:
~/Views/ContentPages/Home/AboutUs.aspx
~/Views/ContentPages/Home/AboutUs.ascx
~/Views/ContentPages/Home/AboutUs.vbhtml
All you now need to do is check the virtualPath that is passed to the GetFiles method, using a database lookup or similar. Here is a simple way:
private bool IsCMSPath(string virtualPath)
{
return virtualPath == "/Views/ContentPages/Home/AboutUs.cshtml" ||
virtualPath == "~/Views/ContentPages/Home/AboutUs.cshtml";
}
public override bool FileExists(string virtualPath)
{
return IsCMSPath(virtualPath) || base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsCMSPath(virtualPath))
{
return new DbVirtualFile(virtualPath);
}
return base.GetFile(virtualPath);
}
The custom virtual file will be made and returned to the browser in the GetFile method.
Finally, a custom view engine can be created to give different virtual view paths that are sent to VirtualPathProvider.
Hope this helps.
I've got my claims set-up with MVC3 using azure and everything is going well.
What I need to do now is extend the Claims Identity that's in the current thread / http context and add my own information (DOB, Address.. that sort of stuff)
so my question is - where is the best place to do this? any examples would be great..
I presume that when the user is authenticated id then have to go to the DB and pull back the relevant record for the user then add it to the custom Claims Identity object?
Typically you will have a httpmodule that will inspect the cookies and once the FedAuth token is found, you have a hook to build your Claims Principal and identities.
You don't typically need to store the user's entire profile, just useful things that wont typically change that often. I do this inside of an actionfilter.
Here is the code I found that does all of this.
https://github.com/wcpro/ScaffR/tree/master/src/ScaffR.Security/content/CodeTemplates/Scaffolders/ScaffR.Security
You may have to do a little digging but its all there.
Here is the code for the http module
public class ClaimsTransformationHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAuthenticateRequest += context_PostAuthenticateRequest;
}
void context_PostAuthenticateRequest(object sender, EventArgs e)
{
var context = ((HttpApplication) sender).Context;
if (FederatedAuthentication.SessionAuthenticationModule != null &&
FederatedAuthentication.SessionAuthenticationModule.ContainsSessionTokenCookie(context.Request.Cookies))
{
return;
}
var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;
if (transformer != null)
{
var transformedPrincipal = transformer.Authenticate(context.Request.RawUrl, context.User as ClaimsPrincipal);
context.User = transformedPrincipal;
Thread.CurrentPrincipal = transformedPrincipal;
}
}
public void Dispose() { }
}
Here is the Claims Transformer
public partial class ClaimsTransformer : ClaimsAuthenticationManager
{
partial void SetCustomPrincipalClaims(IUserService userService, ref ClaimsPrincipal principal);
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return incomingPrincipal;
}
var newPrincipal = Transform(incomingPrincipal);
EstablishSession(newPrincipal);
return newPrincipal;
}
ClaimsPrincipal Transform(ClaimsPrincipal incomingPrincipal)
{
var nameClaim = incomingPrincipal.Identities.First().FindFirst(ClaimTypes.Name);
var userService = DependencyResolver.Current.GetService<IUserService>();
var user = userService.GetByUsername(nameClaim.Value);
var id = new ApplicationIdentity(user);
var principal = new ClaimsPrincipal(id);
SetCustomPrincipalClaims(userService, ref principal);
return principal;
}
private void EstablishSession(ClaimsPrincipal principal)
{
if (HttpContext.Current != null)
{
var sessionToken = new SessionSecurityToken(principal);
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
}
}
Then here is the configuration
<?xml version="1.0" encoding="utf-8"?>
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManager type="Barbarella.Core.Common.Security.ClaimsTransformer, Barbarella.Core" />
</identityConfiguration>
</system.identityModel>
And this...
<system.identityModel.services>
<federationConfiguration>
<cookieHandler mode="Default" requireSsl="false" />
</federationConfiguration>
</system.identityModel.services>
And this...
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true">
<add name="ClaimsTransformationModule" type="Barbarella.Core.Common.Security.ClaimsTransformationHttpModule, Barbarella.Core" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</modules>
Dont forget to add your configuration sections
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
Here is my code for the ApplicationIdentity (overrides ClaimsIDentity)... This is the code that answers your question really...
public sealed partial class ApplicationIdentity : ClaimsIdentity
{
partial void SetCustomIdentityClaims(User user);
private readonly User _user;
public ApplicationIdentity(User user) : base("Application")
{
_user = user;
AddClaim(new Claim(ClaimTypes.Name, user.Username));
AddClaim(new Claim(ApplicationClaimTypes.UserId, user.Id.ToString(CultureInfo.InvariantCulture)));
AddClaim(new Claim(ApplicationClaimTypes.FirstName, user.FirstName));
AddClaim(new Claim(ApplicationClaimTypes.LastName, user.LastName));
AddClaim(new Claim("Time", DateTime.Now.ToString()));
SetCustomIdentityClaims(_user);
}
public User User
{
get { return _user; }
}
public int UserId
{
get { return int.Parse(FindFirst(ApplicationClaimTypes.UserId).Value); }
}
public string Username
{
get { return FindFirst(ClaimTypes.Name).Value; }
}
}