I have a custom AI Telemetry Initializer. Recently, I have started getting a compiler warning, saying Context.Properties is obsolete:
"TelemetryContext.Properties is obsolete. Use GlobalProperties to set global level properties. For properties at item level, use ISupportProperties.Properties.
1. How do I do that? Use ISupportProperties.Properties?
2. I am logging the tenant Id, and deviceID, from claims in the principal, per request. Is that considered app global or a support property?
public class JsonTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly SystemOptions _systemOptions;
/// <summary>
/// Constructor
/// </summary>
/// <param name="accessor">For accessing the http context</param>
/// <param name="systemOptions">System options</param>
public JsonTelemetryInitializer(IHttpContextAccessor accessor, IOptions<SystemOptions> systemOptions)
{
_httpContextAccessor = accessor;
_systemOptions = systemOptions.Value;
}
/// <summary>
/// Initialize the custom telemetry initializer
/// </summary>
/// <param name="telemetry">Telemetry</param>
public void Initialize(ITelemetry telemetry)
{
if (_httpContextAccessor.HttpContext == null)
{
return;
}
if (_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
{
const string tenantId = "northstar_tenantid";
if (!telemetry.Context.Properties.ContainsKey(tenantId))
{
var user = _httpContextAccessor.HttpContext.User;
telemetry.Context.Properties[tenantId] =
ClaimsHelper.GetClaim<int>(user, TokenNames.TenantId).ToString();
var userId = ClaimsHelper.GetClaim<int>(user, TokenNames.UserId).ToString();
telemetry.Context.Properties["northstar_userid"] = userId;
var deviceId = ClaimsHelper.GetClaim<string>(user, TokenNames.DeviceId);
if (deviceId != null)
{
telemetry.Context.Properties["northstar_deviceid"] = deviceId;
}
telemetry.Context.User.Id = userId;
var sessionId = ClaimsHelper.GetClaim<string>(user, TokenNames.SessionId);
if (!string.IsNullOrEmpty(sessionId))
{
telemetry.Context.Session.Id = sessionId;
}
}
}
}
}
1.How do I do that? Use ISupportProperties.Properties?
Just cast the ITelemetry to ISupportProperties.
My example code as below:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
public class MyTelemetryInitializer:ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
//cast ITelemetry to ISupportProperties
ISupportProperties propTelemetry = (ISupportProperties)telemetry;
if (!propTelemetry.Properties.ContainsKey("isuport_key"))
{
propTelemetry.Properties.Add("isuport_key", "isupport_value");
}
}
}
Then after execution, the property is shown in azure portal, screenshot as below:
2.I am logging the tenant Id, and deviceID, from claims in the principal, per request. Is that considered app global or a support property?
As per my understanding, you can use support property(item level) since it's similar with you did previously using like this telemetry.Context.Properties["northstar_deviceid"] = deviceId;
I had a similar problem and find this post.
They added documentation for adding properties to TelemetryContext.
Simply cast ITelemetry to RequestTelemetry.
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
requestTelemetry.Properties["customProp"] = "test";
}
https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-filtering-sampling#addmodify-properties-itelemetryinitializer
I don't see the need for a cast, you could do a type test
For example, in C# 7+:
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if (telemetry is ISupportProperties telemetryWithProperties)
{
telemetryWithProperties.Properties["mykey"] = "test";
}
}
}
Related
In my multitenant application user permissions (read Roles if you are more comfortable with that) are set per tenant so we are adding claims to each user with value TenantName:Permission.
We are using policy based authorization with custom code using the following code
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PermissionAuthorizeAttribute : AuthorizeAttribute
{
public Permission[] AcceptedPermissions { get; set; }
public PermissionAuthorizeAttribute()
{
}
public PermissionAuthorizeAttribute(params Permission[] acceptedPermissions)
{
AcceptedPermissions = acceptedPermissions;
Policy = "RequirePermission";
}
}
public enum Permission
{
Login = 1,
AddUser = 2,
EditOtherUser = 3,
EditBaseData = 6,
EditSettings = 7,
}
With the above code we decorate Controller actions
[PermissionAuthorize(Permission.EditSettings)]
public IActionResult Index()
In startup.cs we have
services.AddAuthorization(options =>
{
options.AddPolicy("RequirePermission", policy => policy.Requirements.Add(new PermissionRequirement()));
});
The AuthorizationHandler would in this case need access to the PermissionAuthorizeAttribute so we can check what permissions were specified on the action. At the moment we can get the attribute with the below code, but I think there must be an easier way, since there is a lot of casting an iterating there.
public class PermissionRequirement : AuthorizationHandler<PermissionRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var filters = ((FilterContext)context.Resource).Filters;
PermissionAuthorizeAttribute permissionRequirement = null;
foreach (var filter in filters)
{
var authorizeFilter = filter as AuthorizeFilter;
if (authorizeFilter == null || authorizeFilter.AuthorizeData == null)
continue;
foreach (var item in authorizeFilter.AuthorizeData)
{
permissionRequirement = item as PermissionAuthorizeAttribute;
if (permissionRequirement != null)
break;
}
if (permissionRequirement != null)
break;
}
//TODO Check that the user has the required claims
context.Succeed(requirement);
return Task.CompletedTask;
}
}
All the examples I have found are like this, where some hardwired policy is specified in startup.cs.
services.AddAuthorization(options =>
{
options.AddPolicy("Over21",
policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
}
Here you decorate you controller or action with
[Authorize(Policy="Over21")]
public class AlcoholPurchaseRequirementsController : Controller
I think the above example would be better if you could specify the age in the controller/action, like this
[Authorize(Policy="OverAge", Age=21)]
public class AlcoholPurchaseRequirementsController : Controller
Now you need to add a different policy for every minimum age.
Any ideas on how to make this efficient?
Although i would use Resource Based Authorization as i commented, there is a way to achieve your goal:
First create a custom attribute:
public class AgeAuthorizeAttribute : Attribute
{
public int Age{ get; set; }
public AgeAuthorizeAttribute(int age)
{
Age = age;
}
}
Then write a filter provider:
public class CustomFilterProvider : IFilterProvider
{
public int Order
{
get
{
return 0;
}
}
public void OnProvidersExecuted(FilterProviderContext context)
{
}
public void OnProvidersExecuting(FilterProviderContext context)
{
var ctrl = context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
var ageAttr = ctrl.MethodInfo.GetCustomAttribute<AgeAuthorizeAttribute>();
if (ageAttr == null)
{
ageAttr = ctrl.ControllerTypeInfo.GetCustomAttribute<AgeAuthorizeAttribute>();
}
if (ageAttr != null)
{
var policy = new AuthorizationPolicyBuilder()
.AddRequirements(new MinimumAgeRequirement(ageAttr.Age))
.Build();
var filter = new AuthorizeFilter(policy);
context.Results.Add(new FilterItem(new FilterDescriptor(filter, FilterScope.Action), filter));
}
}
}
Finally register filter provider:
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFilterProvider, CustomFilterProvider>());
And use it
[AgeAuthorize(21)]
public IActionResult SomeAction()
... or
[AgeAuthorize(21)]
public class AlcoholPurchaseRequirementsController : Controller
ps: not tested
I created a simple service with service stack.
namespace BE.Source.Listener.Services
{
public class StatusService : Service
{
private ILog Logger
{
get
{
return LogManager.GetLogger(GetType()); ;
}
}
public object Get(KilnListenerStatusRequest request)
{
var result = new KilnListenerStatusResponse();
result.LastPushRequest = DateTime.Now;
return result;
}
}
}
The service returns a dto named "StatusResult" which has the ResponseSTatus property.
The Request and the result dtos are in the same name space but not in the one the serivce is,
Or is StatusREsult only filled when a error occurs ?
namespace BE.Source.ServiceModel
{
/// <summary>
/// Request for Service Status
/// </summary>
public sealed class StatusRequest : IReturn<StatusResult>
{
}
}
namespace BE.Source.ServiceModel
{
/// <summary>
///
/// </summary>
public sealed class StatusResult
{
/// <summary>
/// Status of the response
/// </summary>
public ResponseStatus ResponseStatus { get; set; } //Automatic exception handling
}
But when firing the get with the jsonservice cleint the property is null.
To the best of my knowledge the ResponseStatus property will be null when no error has occured.
From one of the many tests in the ServiceStack GitHub repo:
[Test, TestCaseSource(typeof(CustomerServiceValidationTests), "ServiceClients")]
public void Post_ValidRequest_succeeds(Func<IServiceClient> factory)
{
var client = factory();
var response = client.Send<CustomersResponse>(validRequest);
Assert.That(response.ResponseStatus, Is.Null);
}
not sure if I am missing something here. I am using the AppHostHttpListenerBase in a unit test to test a service and in its constructor I pass "api" for the handlerPath parameter. I have a service registered at /hello/{Name} and am using version 3.9.17 of servicestack.
Within the Config method of my appHost class if I access
EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath
it retrurns "api"
Once I am back in the unit test the same call returns null
If I try and call the service with /hello/test it works.
If I use /api/hello/test it fails
It appears that the AppHostHttpListenerBase is loosing the handlerPath ?
Does this sound like a bug or am I missing something ?
below is the code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
namespace Bm.Tests
{
/// <summary>
/// Test self hosting for unit tests
/// </summary>
[TestFixture]
public class TestService
{
private TestServiceAppHost _apphost;
private const string HOST_URL = #"http://localhost:1337/";
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_apphost = new TestServiceAppHost();
_apphost.Init();
_apphost.Start(HOST_URL);
}
[Test]
public void TestHelloServiceJson()
{
var prefix = EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath;
Assert.AreEqual("api", prefix, "Should be api");
var client = new JsonServiceClient(HOST_URL);
var response = client.Send<HelloResponseTest>(new HelloTest() { Name = "Todd" });
Assert.AreEqual("Hello, Todd", response.Result);
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
_apphost.Stop();
_apphost.Dispose();
}
}
public class HelloTest
{
public string Name { get; set; }
}
public class HelloResponseTest
{
public string Result { get; set; }
}
public class HelloServiceTest : ServiceBase<HelloTest>
{
protected override object Run(HelloTest request)
{
return new HelloResponseTest { Result = "Hello, " + request.Name };
}
}
//Define the Web Services AppHost
public class TestServiceAppHost : AppHostHttpListenerBase
{
public TestServiceAppHost() : base("testing HttpListener", "api", typeof(HelloServiceTest).Assembly) { }
public override void Configure(Funq.Container container)
{
// this works and returns api
var prefix = EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath;
Routes
.Add<HelloTest>("/hello")
.Add<HelloTest>("/hello/{Name}");
}
}
}
If you want the handler root path to be /api you need to add that to the listener url, i.e:
_apphost.Start("http://localhost:1337/api/");
I'm creating a self-hosted REST service using service stack & AppHostHttpListenerBase. I'd like to use a base URI for my services (e.g. "api") like so:
http://myserver/api/service1/param
http://myserver/api/service2/param
How do I do this without defining "api" in each of my routes. In IIS, I can set a virtual directory to isolate the services, but how do I do this when self-hosting?
Here ya go.. (as a bonus this is how you put your service into a plugin.
using BlogEngineService;
using ServiceStack.WebHost.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BlogEngineWinService
{
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Self Host Service", typeof(AppHost).Assembly) { }
public override void Configure(Funq.Container container)
{
Plugins.Add(new BlogEngine());
}
}
}
This is how you autowire it up
The call appHost.Routes.AddFromAssembly2(typeof(HelloService).Assembly); Is what calls the extension to auto wire.
using ServiceStack.WebHost.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.ServiceInterface;
namespace BlogEngineService
{
public class BlogEngine : IPlugin, IPreInitPlugin
{
public void Register(IAppHost appHost)
{
appHost.RegisterService<HelloService>();
appHost.Routes.AddFromAssembly2(typeof(HelloService).Assembly);
}
public void Configure(IAppHost appHost)
{
}
}
}
This is how you mark the Service Class to give it a prefix.
Simply mark the class with this attribute
using ServiceStack.DataAnnotations;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BlogEngineService
{
public class Hello
{
[PrimaryKey]
public string Bob { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
[PrefixedRoute("/test")]
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Bob};
}
}
}
Create a CS file in your project for the extension..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using ServiceStack.Common;
using ServiceStack.Common.Utils;
using ServiceStack.Common.Web;
using ServiceStack.Text;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints;
using ServiceStack.ServiceInterface;
namespace ServiceStack.ServiceInterface
{
public static class ServiceRoutesExtensions
{
/// <summary>
/// Scans the supplied Assemblies to infer REST paths and HTTP verbs.
/// </summary>
///<param name="routes">The <see cref="IServiceRoutes"/> instance.</param>
///<param name="assembliesWithServices">
/// The assemblies with REST services.
/// </param>
/// <returns>The same <see cref="IServiceRoutes"/> instance;
/// never <see langword="null"/>.</returns>
public static IServiceRoutes AddFromAssembly2(this IServiceRoutes routes,
params Assembly[] assembliesWithServices)
{
foreach (Assembly assembly in assembliesWithServices)
{
AddNewApiRoutes(routes, assembly);
}
return routes;
}
private static void AddNewApiRoutes(IServiceRoutes routes, Assembly assembly)
{
var services = assembly.GetExportedTypes()
.Where(t => !t.IsAbstract
&& t.HasInterface(typeof(IService)));
foreach (Type service in services)
{
var allServiceActions = service.GetActions();
foreach (var requestDtoActions in allServiceActions.GroupBy(x => x.GetParameters()[0].ParameterType))
{
var requestType = requestDtoActions.Key;
var hasWildcard = requestDtoActions.Any(x => x.Name.EqualsIgnoreCase(ActionContext.AnyAction));
string allowedVerbs = null; //null == All Routes
if (!hasWildcard)
{
var allowedMethods = new List<string>();
foreach (var action in requestDtoActions)
{
allowedMethods.Add(action.Name.ToUpper());
}
if (allowedMethods.Count == 0) continue;
allowedVerbs = string.Join(" ", allowedMethods.ToArray());
}
if (service.HasAttribute<PrefixedRouteAttribute>())
{
string prefix = "";
PrefixedRouteAttribute a = (PrefixedRouteAttribute)Attribute.GetCustomAttribute(service, typeof(PrefixedRouteAttribute));
if (a.HasPrefix())
{
prefix = a.GetPrefix();
}
routes.AddRoute(requestType, allowedVerbs, prefix);
}
else
{
routes.AddRoute(requestType, allowedVerbs);
}
}
}
}
private static void AddRoute(this IServiceRoutes routes, Type requestType, string allowedVerbs, string prefix = "")
{
var newRoutes = new ServiceStack.ServiceHost.ServiceRoutes();
foreach (var strategy in EndpointHost.Config.RouteNamingConventions)
{
strategy(newRoutes, requestType, allowedVerbs);
}
foreach (var item in newRoutes.RestPaths)
{
string path = item.Path;
if (!string.IsNullOrWhiteSpace(prefix))
{
path = prefix + path;
}
routes.Add(requestType, restPath: path, verbs: allowedVerbs);
}
}
}
public class PrefixedRouteAttribute : Attribute
{
private string _prefix { get; set; }
private bool _hasPrefix { get; set; }
public PrefixedRouteAttribute(string path)
{
if (!string.IsNullOrWhiteSpace(path))
{
this._hasPrefix = true;
this._prefix = path;
//this.Path = string.Format("/{0}{1}", Prefix, Path);
}
}
public bool HasPrefix()
{
return this._hasPrefix;
}
public string GetPrefix()
{
return this._prefix;
}
}
}
ServiceStack's HttpListener hosts expects to be hosted a the root / path as the normal use-case is to have each self-hosted service available on different custom ports.
Since it doesn't currently support hosting at a /custompath, you would have to specify /api/ prefix on all your service routes.
Add an issue if you want to see support for hosting at custom paths.
There is actually an easier solution. In your web.config, update your http-handler to:
<httpHandlers>
<add path="api*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" />
</httpHandlers>
With the above, all of your service apis must be prefixed with a "/api/". If you have already used "/api/" in any of your routes, you must now remove them or have to specify it twice in your calls.
Reference:
https://github.com/ServiceStack/SocialBootstrapApi
I've found a workaround for this. I've only tested this under self hosting.
Create a 'PrefixedRouteAttribute' class that inherits from RouteAttribute
public class PrefixedRouteAttribute : RouteAttribute
{
public static string Prefix { get; set; }
public PrefixedRouteAttribute(string path) :
base(path)
{
SetPrefix();
}
public PrefixedRouteAttribute(string path, string verbs)
: base(path, verbs)
{
SetPrefix();
}
private void SetPrefix()
{
if (!string.IsNullOrWhiteSpace(Prefix))
{
this.Path = string.Format("/{0}{1}", Prefix, Path);
}
}
}
When you create your AppHost you can set your Prefix
PrefixedRouteAttribute.Prefix = "api";
Then instead of using the [Route] attribute, use the [PrefixRoute] attribute on your classes
[PrefixedRoute("/echo")]
[PrefixedRoute("/echo/{Value*}")]
public class Echo
{
[DataMember]
public string Value { get; set; }
}
This will then work for requests to
/api/echo
/api/echo/1
This could possibly be improved. I don't really like the how I need to set the Prefix via the static property but I couldn't think of a better approach under my setup. The principle of creating the overriding attribute seems sound though, and that is the important part.
I am having trouble implementing ASPNetIdentity on my MVC project
I am getting this error in the var line (The entity type IdentityRole is not part of the model for the current context.):
using System;
using System.Linq;
using System.Web.Mvc;
using EZ.Models;
using Microsoft.AspNet.Identity.EntityFramework;
namespace EZ.Controllers
{
public class RoleController : Controller
{
ApplicationDbContext context;
public RoleController()
{
context = new ApplicationDbContext();
}
/// <summary>
/// Get All Roles
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
var roles = context.Roles.ToList();
return View(roles);
}
/// <summary>
/// Create a New role
/// </summary>
/// <returns></returns>
// GET: /Roles/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
Name = collection["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully !";
return RedirectToAction("Index");
}
catch
{
return View();
}
}
/// <summary>
/// Set Role for Users
/// </summary>
/// <returns></returns>
public ActionResult SetRoleToUser()
{
var list = context.Roles.OrderBy(role => role.Name).ToList().Select(role => new SelectListItem { Value = role.Name.ToString(), Text = role.Name }).ToList();
ViewBag.Roles = list;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UserAddToRole(string uname, string rolename)
{
ApplicationUser user = context.Users.Where(usr => usr.UserName.Equals(uname, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
// Display All Roles in DropDown
var list = context.Roles.OrderBy(role => role.Name).ToList().Select(role => new SelectListItem { Value = role.Name.ToString(), Text = role.Name }).ToList();
ViewBag.Roles = list;
if (user != null)
{
var account = new AccountController();
account.UserManager.AddToRoleAsync(user.Id, rolename);
ViewBag.ResultMessage = "Role created successfully !";
return View("SetRoleToUser");
}
else
{
ViewBag.ErrorMessage = "Sorry user is not available";
return View("SetRoleToUser");
}
}
}
}
I have scripted the tables in my DB.
This is the exact same code as in the role-security-mvc5-master project from CodeProject.com. The only difference is that I moved the tables in my DB ana dI changed the connection string. What is the piece I am missing?
in my IdentityModel.cs I have:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
If you need more code, please let me know and I will post.
When the project was created, identity, a database at locadb. when I moved the tables in my DB I overwrote the entire Default connection string with the one I created for E. The big probelm here was that the default connection needs providerName="System.Data.SqlClient" /> and NOT providerName="System.Data.EntityClient". Be careful there. Credit needs to go to this excellent article from Daniel Eagle: http://danieleagle.com/blog/2014/05/setting-up-asp-net-identity-framework-2-0-with-database-first-vs2013-update-2-spa-template/. Tons of detail on how to use Identity with DB first.