Configure ServiceStack Base URI - servicestack

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.

Related

Can I use AutoMapper with Blazor?

Can I use AutoMapper 8.0.1 with Blazor server app, please?
I have try it but my code always run into an error:
Missing type map configuration or unsupported mapping. Mapping types:
Object -> Object System.Object -> System.Object
I have added the mapper to the Startup file:
services.AddAutoMapper(typeof(Startup));
I have created the profile:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<District, DistrictModel>();
}
}
And I try to use it:
[Inject]
protected IMapper Mapper { get; set; }
District district = DistrictService.FindDistrictById(districtId);
DistrictModel model = Mapper.Map<DistrictModel>(district);
The AssertConfigurationIsValid method gives:
Cannot find any profiles with the name 'MyProfile'. (Parameter 'profileName')
Add this in your services in startup :
it's reusable and cleaner
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
add these to interface and class in your project
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces()
.Any(i =>i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
your model or viewmodel :
public class District : IMapFrom<District>
{
public string PhoneNumber { get; set; }
public string Password { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<District, DistrictModel>();
}
}
Startup.cs
var mapperConfiguration = new MapperConfiguration(configuration =>
{
configuration.AddProfile(new MyProfile());
});
var mapper = mapperConfiguration.CreateMapper();
services.AddSingleton(mapper);

Authorize if not in specific role attribute MVC 5

I need authorization attribute for action which allows all but specific role.
something like
[!Authorize(Roles = "SuperUser")]
public ActionResult PaySuperUser.....
Anything built in?
Or any suggestion for custom attribute?
I think a custom attribute is a way to go.
Here is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
namespace YourFancyNamespace
{
public class AuthorizeExtended : AuthorizeAttribute
{
private string _notInRoles;
private List<string> _notInRolesList;
public string NotInRoles
{
get
{
return _notInRoles ?? string.Empty;
}
set
{
_notInRoles = value;
if (!string.IsNullOrWhiteSpace(_notInRoles))
{
_notInRolesList = _notInRoles
.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim()).ToList();
}
}
}
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
if (_notInRolesList != null && _notInRolesList.Count > 0)
{
foreach (var role in _notInRolesList)
{
if (actionContext.RequestContext.Principal.IsInRole(role))
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
}
}
}
}
And here is how you can use it:
// A AuthorizeExtended equals Authorize(with Role filter) + exclude all pesky users
[AuthorizeExtended(Roles = "User", NotInRoles="PeskyUser")]
[HttpPost]
[Route("api/Important/DoNotForgetToUpvote")]
public async Task<IHttpActionResult> DoNotForgetToUpvote()
{
return Ok("I did it!");
}
// Б AuthorizeExtended equals plain Authorize + exclude all pesky users
[AuthorizeExtended(NotInRoles="PeskyUser")]
[HttpPost]
[Route("api/Important/DoNotForgetToUpvote")]
public async Task<IHttpActionResult> DoNotForgetToUpvote()
{
return Ok("I did it!");
}
// В AuthorizeExtended equals Authorize
[AuthorizeExtended]
[HttpPost]
[Route("api/Important/DoNotForgetToUpvote")]
public async Task<IHttpActionResult> DoNotForgetToUpvote()
{
return Ok("I did it!");
}

Automapper ObservableCollection – refreshing is not working

I have small WPF application. There are 5 projects in solution.
I want separate DOMAIN classes with UI ENTITIES and I want to use AUTOMAPPER.
You can download whole solution here: TestWPFAutomapper.zip
Domain class(Domain.Source.cs) with UI Entity(Entities.Destination.cs) have same signature.
In Entities.Destination.cs I would like to put other logic.
namespace DOMAIN
{
public class Source
{
public int Id { get; set; }
public int Position { get; set; }
}
}
using System.ComponentModel;
namespace ENITITIES
{
public class Destination : INotifyPropertyChanged
{
private int _id;
private int _position;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public int Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
My data comes from DAL.DataContext using Entity Framework with CodeFirst. Here I´m using Source class.
using System.Data.Entity;
using DOMAIN;
namespace DAL
{
public class DataContext : DbContext
{
public DbSet<Source> Sources { get; set; }
}
}
Mapping is in BL.MyAppLogic.cs . In this class I have property Items which is ObservableCollection.
After puting another item into DB for Source class collection get refresh but for Destination is not refreshing.
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using AutoMapper;
using DAL;
using DOMAIN;
using ENITITIES;
namespace BL
{
public class MyAppLogic
{
private readonly DataContext _dataContext = new DataContext();
public ObservableCollection<Source> Items { get; set; }
//public ObservableCollection<Destination> Items { get; set; }
public MyAppLogic()
{
Database.SetInitializer(new MyInitializer());
Mapping();
_dataContext.Sources.Load();
Items = _dataContext.Sources.Local;
//Items = Mapper.Map<ObservableCollection<Source>, ObservableCollection<Destination>>(_dataContext.Sources.Local);
}
private void Mapping()
{
Mapper.CreateMap<Source, Destination>().ReverseMap();
// I tried also Mapper.CreateMap<ObservableCollection<Source>, ObservableCollection<Destination>>().ReverseMap();
}
public int GetLastItem()
{
return _dataContext.Database.SqlQuery<int>("select Position from Sources").ToList().LastOrDefault();
}
public void AddNewItem(Destination newItem)
{
_dataContext.Sources.Add(Mapper.Map<Destination, Source>(newItem));
_dataContext.SaveChanges();
}
}
}
My problem is not with mapping, that’s works good, but with refreshing collection after adding or removing items from db. If I use DOMAIN.Source class everything works, collection is refreshing. But when I’m using ENTITIES.Destination data comes from DB and also I can put som new data to DB but refresing ObservableCollection is not working.
Please try to comment lines(14 & 23) in BL.MyAppLogic.cs and uncomment(15 & 24) and you’ll see what I mean.
Thank you for any help.
I got it but I don´t know if is correct.
Local has CollectionChanged event
so in constructor I put these lines
public MyAppLogic()
{
Database.SetInitializer(new MyInitializer());
Mapping();
_dataContext.Sources.Load();
_dataContext.Sources.Local.CollectionChanged += SourcesCollectionChanged;
Items = Mapper.Map<ObservableCollection<Source>, ObservableCollection<Destination>>(_dataContext.Sources.Local);
}
and handler looks
private void SourcesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var source = sender as ObservableCollection<Source>;
Mapper.Map(source, Items);
}
Now is my collection automating refreshing when I put something to DB in my UI.
Looks like automapper don´t put reference into Items, but create new instance.

ServiceStack metadata descriptions missing

In a given example code below the metadata page never gets the description specified in
[Description("GET account, all or by list of groups or by list of logins")]
Is there a special config that needs to be set in order for descriptions to show up in the metadata pages?
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using ServiceStack.ServiceHost;
using System.Runtime.Serialization;
using ServiceStack.WebHost.Endpoints;
namespace ConsoleApplication2
{
public class User
{
public User()
{
}
public int login;
public string group;
public string name;
}
[Description("GET account, all or by list of groups or by list of logins")]
[Route("/accounts")]
public class Accounts : IReturn<List<User>>
{
public string[] groups { set; get; }
public int[] logins { set; get; }
public Accounts() { }
public Accounts(params int[] logins)
{
this.logins = logins;
}
public Accounts(params string[] groups)
{
this.groups = groups;
}
}
public class Host : AppHostHttpListenerBase
{
public Host() : base("Test",
typeof(Accounts).Assembly)
{
}
public override void Configure(Funq.Container container)
{
SetConfig(new EndpointHostConfig {
EnableFeatures = Feature.All
});
}
}
public class Servce : IService
{
public object Get(Accounts request)
{
return new List<User>(){new User()};
}
}
class Program
{
static void Main(string[] args)
{
var host = new Host();
host.Init();
host.Start("http://+:12345/");
Console.ReadLine();
}
}
}
Navigating to http://localhost:12345/json/metadata?op=Accounts produces
<body>
<a id="logo" href="http://www.servicestack.net" title="servicestack"></a>
<h1>Test</h1>
<form>
<div>
<p><back to all web services</p>
<h2>Accounts</h2>
<div class="example">
<!-- REST Examples -->
...
In a recent release of ServiceStack, [Description] was deprecated in favour of [Api] and [ApiMember] which are also used in ServiceStack's Swagger support.
This is now an example of a fully annotated service:
[Api("Service Description")]
[Route("/swagger/{Name}", "GET", Summary = #"GET Summary", Notes = "GET Notes")]
[Route("/swagger/{Name}", "POST", Summary = #"POST Summary", Notes = "POST Notes")]
public class MyRequestDto
{
[ApiMember(Name="Name", Description = "Name Description",
ParameterType = "path", DataType = "string", IsRequired = true)]
public string Name { get; set; }
}

servicestack AppHostHttpListenerBase handlerpath parameter not working?

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/");

Resources