I added the RazorPlugin along with a Test.cshtml in the root View folder (tried wwwroot as well along with TestGet.cshtml). This is an donet Core project. Whenever browsing to: /api/test the following error is generated:
Snapshot of TestGet generated by ServiceStack on 6/3/2017 4:20:40 PM
view json datasource from original url:
http://localhost:51550/api/test? in other formats: json xml csv jsv
Response Status Error CodeNullReferenceExceptionMessageObject
reference not set to an instance of an object.Stack Trace[TestGet:
6/3/2017 4:20:40 PM]: [REQUEST: {}] System.NullReferenceException:
Object reference not set to an instance of an object. at
ServiceStack.Mvc.RazorFormat.FindView(IEnumerable1 viewNames) in
/opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack.Mvc/RazorFormat.cs:line
174 at ServiceStack.Mvc.RazorFormat.ProcessRequest(IRequest req,
IResponse res, Object dto) in
/opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack.Mvc/RazorFormat.cs:line
138 at System.Linq.Enumerable.Any[TSource](IEnumerable1 source,
Func`2 predicate) at
ServiceStack.Formats.HtmlFormat.SerializeToStream(IRequest req, Object
response, IResponse res) in
/opt/lib/teamcity-agent/work/d09206570215629/src/ServiceStack/Formats/HtmlFormat.cs:line
63Errors
The other formats work (json/etc).
public class Test
{
public string ExternalId { get; set; }
}
[Authenticate, Route("/test")]
public class TestGet : IGet, IReturn<Test>
{
}
public class TestService : Service
{
public IAutoQueryDb _autoQueryDb { get; set; }
public IDbConnectionFactory _dbFactory { get; set; }
public TestService(IDbConnectionFactory dbFactory)
{
_dbFactory = dbFactory;
}
public Test Get(TestGet request)
{
var test = new Test();
test.ExternalId = "abc";
return test;
}
}
Test.cshtml
#using App.Shared.DomainModel
#model Test
<div>Test</div>
AppHost.cs
using App.DomainServices;
using App.FontEnd.Infrastructure.Configuration;
using App.FontEnd.Infrastructure.Core;
using App.Shared.DomainModel;
using Funq;
using LightInject;
using ServiceStack;
using ServiceStack.Admin;
using ServiceStack.Api.Swagger;
using ServiceStack.Auth;
using ServiceStack.Caching;
using ServiceStack.Configuration;
using ServiceStack.Data;
using ServiceStack.Mvc;
using ServiceStack.OrmLite;
using ServiceStack.Validation;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace App.FrontEnd
{
public class AppHost : AppHostBase
{
/// <summary>
/// Base constructor requires a Name and Assembly where web service implementation is located
/// </summary>
public AppHost()
: base("TestApi", typeof(CompanyService).GetAssembly())
{
}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
public override void Configure(Container container)
{
this.GlobalRequestFilters.Add((httpReq, httpResp, requestDto) =>
{
var currentSession = httpReq.GetSession();
if (currentSession != null)
{
RequestContext.Instance.Items.Add("CurrentUserName", currentSession.UserName);
RequestContext.Instance.Items.Add("CurrentUserId", currentSession.UserAuthId);
}
});
ServiceContainer LightContainer = new ServiceContainer();
LightContainer.RegisterInstance<IDbConnectionFactory>
(
new OrmLiteConnectionFactory(
"removed",
SqlServer2014Dialect.Provider
)
);
LightContainer.Register<IDbConnection>(c =>
c.GetInstance<IDbConnectionFactory>().OpenDbConnection(),
new PerScopeLifetime()
);
LightContainer.Register<OrmLiteAppSettings>(c =>
new OrmLiteAppSettings(c.GetInstance<IDbConnectionFactory>()));
LightContainer.Register<ServiceStack.Web.IServiceGatewayFactory>(x => new ApiServiceGatewayFactory());
container.Adapter = new LightInjectAdapter(LightContainer);
var settings = LightContainer.GetInstance<OrmLiteAppSettings>();
settings.InitSchema();
AppSettings = new MultiAppSettings(
settings
);
container.Register<ICacheClient>(new OrmLiteCacheClient
{
DbFactory = LightContainer.GetInstance<IDbConnectionFactory>()
});
var cacheclient = container.Resolve<ICacheClient>();
cacheclient.InitSchema();
AuthConfig(container, AppSettings);
Plugins.Add(new RegistrationFeature());
Plugins.Add(new SwaggerFeature());
Plugins.Add(new RequestLogsFeature());
Plugins.Add(new PostmanFeature());
Plugins.Add(new CorsFeature(allowCredentials: true));
Plugins.Add(new ValidationFeature());
Plugins.Add(new RazorFormat());
OrmLiteConfig.InsertFilter = (dbCmd, row) =>
{
var auditRow = row as CoreModel;
if (auditRow != null)
{
var currentDate = DateTime.UtcNow;
var insertUserId = RequestContext.Instance.Items["CurrentUserId"] as string;
auditRow.Id = Guid.NewGuid();
auditRow.CreatedDate = currentDate;
auditRow.CreatedBy = insertUserId;
auditRow.UpdatedDate = currentDate;
auditRow.UpdatedBy = insertUserId;
}
};
OrmLiteConfig.UpdateFilter = (dbCmd, row) =>
{
var auditRow = row as CoreModel;
if (auditRow != null)
{
var updateUserId = RequestContext.Instance.Items["CurrentUserId"] as string;
auditRow.UpdatedDate = DateTime.UtcNow;
auditRow.UpdatedBy = updateUserId;
}
};
var aq = new AutoQueryFeature { MaxLimit = 100, EnableAutoQueryViewer = true };
aq.ImplicitConventions.Add("%neq", aq.ImplicitConventions["%NotEqualTo"]);
aq.ImplicitConventions.Add("%eq", "{Field} = {Value}");
Plugins.Add(aq);
Plugins.Add(new AdminFeature());
SetConfig(new HostConfig
{
HandlerFactoryPath = "api",
DebugMode = true
});
container.CheckAdapterFirst = true;
//Set up service stack validators
container.ValidatorsSetup();
}
public void AuthConfig(Container container, IAppSettings settings)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(AppSettings),
new JwtAuthProvider(AppSettings)
{
AuthKeyBase64 = "abcdefgh"
},
new BasicAuthProvider()
}));
var authRepo = CreateOrmLiteAuthRepo(container, settings);
}
private static IUserAuthRepository CreateOrmLiteAuthRepo(Container container, IAppSettings appSettings)
{
//Store User Data into the referenced SqlServer database
container.Register<IAuthRepository>(c =>
new OrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>()));
//Use OrmLite DB Connection to persist the UserAuth and AuthProvider info
var authRepo = (OrmLiteAuthRepository)container.Resolve<IAuthRepository>();
//If using and RDBMS to persist UserAuth, we must create required tables
if (appSettings.Get("RecreateAuthTables", false))
authRepo.DropAndReCreateTables(); //Drop and re-create all Auth and registration tables
else
authRepo.InitSchema(); //Create only the missing tables
return authRepo;
}
}
}
I've created a minimal verifiable MVC test project to simulate your configuration which is working as expected at: https://github.com/NetCoreApps/scratch/tree/master/src/RazorApi
It includes the same Test Services, (sans [Authenticate] attribute):
public class Test
{
public string ExternalId { get; set; }
}
[Route("/test")]
public class TestGet : IGet, IReturn<Test>
{
}
public class TestService : Service
{
public Test Get(TestGet request)
{
var test = new Test { ExternalId = "abc" };
return test;
}
}
And the minimal AppHost configuration which just sets the HandlerFactoryPath to api and registers ServiceStack's RazorFormat plugin:
public class AppHost : AppHostBase
{
public AppHost()
: base("ServiceStack + .NET Core", typeof(MyServices).GetTypeInfo().Assembly) {}
public override void Configure(Funq.Container container)
{
SetConfig(new HostConfig
{
HandlerFactoryPath = "api",
DebugMode = true,
});
Plugins.Add(new RazorFormat());
}
}
With the Test.cshtml maintained in /Views/Test.cshtml:
#model Test
#{
Layout = "_Layout";
}
<h1>Test.cshtml</h1>
<p>#Model.ExternalId</p>
Which works as expected with the Razor View executed when calling:
http://localhost:5000/api/test
Which also works when renamed to match the Request DTO at TestGet.cshtml.
As the issue seems specific to your project I'd compare your layout with bare RazorApi Github Project to see if you can find any differences, failing that I'd recommend commenting out configuration to get it to a working state then uncomment sections at a time to find out which configuration is causing the issue.
Related
I am providing my endpoint with a correlation ID:
I then read that ID from the HttpContext.Request.Headers and use that as my telemetry.Context.Operation.Id.
This works, but when I look in my log I have an extra entry which is auto generated by the framework. This entry has its own ID. How can I ensure the framework useses the same ID?
This is how I configure the service
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Users.Api.Services;
using Users.Api.Utility;
using Users.Services.Implementations;
using Users.Services.Interfaces;
using Users.Sql;
using Users.Utility;
using Packages.Api.Filters;
using Packages.Audit;
using Swashbuckle.AspNetCore.Swagger;
namespace Users.Api
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Dependency injection
services.AddAutoMapper();
services.AddDbContext<UsersContext>(
builder => builder.UseSqlServer(Environment.GetEnvironmentVariable("Connectionstring")));
services.AddScoped<IUserService, UserService>();
services.AddScoped<IIdentityService, IdentityService>();
services.AddScoped<IServiceBusCommunicator, ServiceBusCommunicator>();
services.AddScoped<IGraphClient, GraphClient>();
services.AddScoped<IClaimsHarvester, ClaimsHarvester>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddSingleton<HttpClient>();
services.AddSingleton<EndpointConfiguration>();
services.AddSingleton<GraphConfiguration>();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IAuditLogClient, AuditLogClient>();
var clientId = Environment.GetEnvironmentVariable("Authentication:AzureAd:ClientId");
var tenant = Environment.GetEnvironmentVariable("Authentication:AzureAd:Tenant");
var signInPolicyId = Environment.GetEnvironmentVariable("Authentication:AzureAd:SignInPolicyId");
var authority = $"https://login.microsoftonline.com/tfp/{tenant}/{signInPolicyId}/v2.0/";
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(bearerOptions =>
{
bearerOptions.Authority = authority;
bearerOptions.Audience = clientId;
bearerOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
services.AddMvc();
services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new Info { Title = "Users API", Version = "v1" });
});
services.ConfigureSwaggerGen(options =>
{
options.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
options.OperationFilter<CorrelationHeaderParameterOperationFilter>();
options.OperationFilter<XTotalCountHeaderParameterOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
ContextInitializer contextInitializer)
{
if (env.IsDevelopment())
{
// loggerFactory.AddConsole(Configuration.GetSection("Logging"));
// loggerFactory.AddDebug();
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(
c =>
{
c.SwaggerEndpoint($"{Environment.GetEnvironmentVariable("ServiceFabric:UniqueUrlPath")}/swagger/v1/swagger.json", "Contacts API V1");
});
// Seed default values
contextInitializer.Seed();
}
private Task AuthenticationFailed(AuthenticationFailedContext arg)
{
// For debugging purposes only!
var s = $"AuthenticationFailed: {arg.Exception.Message}";
arg.Response.ContentLength = s.Length;
arg.Response.Body.Write(Encoding.UTF8.GetBytes(s), 0, s.Length);
return Task.FromResult(0);
}
}
}
This is not generally supported by ApplicationInsights.
You may still achieve it, but have to write a custom request collection.
You'd need to remove RequestTelemetryTrackingModule from the DI container and add your custom middleware that tracks requests.
What will not work with this approach (code is below):
scenarios when you use different instrumentation keys on this service and upstream services. You can check out how it is handled in AppInsights SDK ( set requestTelemetry.Source and response header)
correlation with informational traces emitted by AspNetCore.
normally, request telemetry name contains route rather than path, you might need to figure it out
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry("ikey");
var requestModule =
services.FirstOrDefault(sd => sd.ImplementationType == typeof(RequestTrackingTelemetryModule));
if (requestModule != null)
{
services.Remove(requestModule);
}
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, TelemetryClient client)
{
app.UseMiddleware<RequestMiddleware>(client);
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.UseHttpsRedirection();
app.UseMvc();
}
}
public class RequestMiddleware
{
private readonly RequestDelegate next;
private readonly TelemetryClient telemetryClient;
public RequestMiddleware(
RequestDelegate next,
TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var activity = new Activity("request");
if (context.Request.Headers.TryGetValue("x-my-correlation-id", out var val))
{
activity.SetParentId(val);
}
using (var request = telemetryClient.StartOperation<RequestTelemetry>(activity))
{
request.Telemetry.Url = context.Request.GetUri();
request.Telemetry.Context.Operation.Name = $"{context.Request.Method} {context.Request.Path.Value}";
request.Telemetry.Name = $"{context.Request.Method} {context.Request.Path.Value}";
try
{
await next.Invoke(context).ConfigureAwait(false);
}
catch (Exception e)
{
telemetryClient.TrackException(e);
request.Telemetry.Success = false;
throw;
}
finally
{
if (context.Response != null)
{
request.Telemetry.ResponseCode = context.Response.StatusCode.ToString();
request.Telemetry.Success = context.Response.StatusCode < 400;
}
else
{
request.Telemetry.Success = false;
}
}
}
}
}
I am getting an error on SendAll in a unittest
This works fine...
using (var service = HostContext.ResolveService<DeviceService>(authenticatedRequest))
{
service.Put(new AddConfig { ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0 });
}}
ServiceStack.WebServiceException: 'The operation 'AddConfig[]' does not exist for this service'
//DeviceConfig
/// <summary>
/// To insert new Config
/// </summary>
/// <returns> New row Id or -1 on error</returns>
public long Any(AddConfig request)
{
try
{
//convert request to model
var perm = request.ConvertTo<DeviceConfig>();
//log user
perm.AuditUserId = UserAuth.Id;
//insert data
var insert = Db.Insert(perm, selectIdentity:true);
//log inserted data
LogInfo(typeof(DeviceConfig), perm, LogAction.Insert);
return insert;
}
//on error log error and data
catch (Exception e)
{
Log.Error(e);
}
return -1;
}
[Route("/Config", "PUT")]
public class AddConfig : IReturn<long>
{
public int DeviceId { get; set; }
public string ConfigName { get; set; }
public string ConfigValue { get; set; }
}
public const string TestingUrl = "http://localhost:5123/";
public void DeviceX400Test(string deviceTemaplateFile)
{
//Resolve auto-wired service
WireUpService<DeviceService>();
var requests = new[]
{
new AddConfig { ConfigName = "Foo" },
new AddConfig { ConfigName = "Bar" },
new AddConfig { ConfigName = "Baz" },
};
var client = new JsonServiceClient(TestingUrl);
var deviceConfigs = client.SendAll(requests);
}
MY ServiceBase for Unit Testting that builds from my .netcore appsettings.Json file
public abstract class ServiceTestBase: IDisposable
{
//private readonly ServiceStackHost appHost;
public BasicRequest authenticatedRequest;
public const string TestingUrl = "http://localhost:5123/";
public SeflHostedAppHost apphost;
public ServiceTestBase()
{
var licenseKeyText = "********************************";
Licensing.RegisterLicense(licenseKeyText);
apphost = (SeflHostedAppHost) new SeflHostedAppHost()
.Init()
.Start(TestingUrl);
//regsiter a test user
apphost.Container.Register<IAuthSession>(c => new AuthUserSession { FirstName = "test", IsAuthenticated = true });
}
public void WireUpService<T>() where T : class
{
//var service = apphost.Container.Resolve<T>(); //Resolve auto-wired service
apphost.Container.AddTransient<T>();
authenticatedRequest = new BasicRequest
{
Items = {
[Keywords.Session] = new AuthUserSession { FirstName = "test" , UserAuthId="1", IsAuthenticated = true}
}
};
}
public virtual void Dispose()
{
apphost.Dispose();
}
}
//Create your ServiceStack AppHost with only the dependencies your tests need
/// <summary>
/// This class may need updates to match what is in the mvc.service apphost.cs
/// </summary>
public class SeflHostedAppHost : AppSelfHostBase
{
public IConfigurationRoot Configuration { get; set; }
public SeflHostedAppHost() : base("Customer REST Example", typeof(StartupService).Assembly) { }
public override void Configure(Container container)
{
var file = Path.GetFullPath(#"../../../../cbw.services");
var builder = new ConfigurationBuilder().SetBasePath(file).AddJsonFile("appsettings.json").AddJsonFile("appsettings.LocalSQLServer.json", optional: true);
Configuration = builder.Build();
var sqlString = Configuration["ConnectionString"];
RegisterServiceStack();
//container.Register<ServiceStack.Data.IDbConnectionFactory>(new OrmLiteConnectionFactory(sqlString,SqlServerDialect.Provider));
container.Register<IDbConnectionFactory>(new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider));
container.RegisterAutoWired<DatabaseInitService>();
var service = container.Resolve<DatabaseInitService>();
container.Register<IAuthRepository>(c =>
new MyOrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>())
{
UseDistinctRoleTables = true,
});
container.Resolve<IAuthRepository>().InitSchema();
var authRepo = (OrmLiteAuthRepository)container.Resolve<IAuthRepository>();
service.ResetDatabase();
SessionService.ResetUsers(authRepo);
service.InitializeTablesAndData();
//Logging
LogManager.LogFactory = new SerilogFactory(new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.Destructure.UsingAttributes()
.CreateLogger());
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
Serilog.Debugging.SelfLog.Enable(Console.Error);
ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
//ILog Log = LogManager.GetLogger(typeof(StartupService));
log.InfoFormat("Applicaiton Starting {Date}", DateTime.Now);
}
public void RegisterServiceStack()
{
var licenseKeyText = "****************************";
Licensing.RegisterLicense(licenseKeyText);
}
}
My Xunit Test
public class DeviceTemplateTest : ServiceTestBase
{
//Post Data
//Device Sends State.XML
[Theory]
[InlineData("C:\\DeviceTemplate.txt")]
public void DeviceX400Test(string deviceTemaplateFile)
{
//Resolve auto-wired service
WireUpService<DeviceService>();
var parser = new FileIniDataParser();
IniData data = parser.ReadFile(deviceTemaplateFile);
List<AddConfig> batch = new List<AddConfig>();
//Iterate through all the sections
foreach (SectionData section in data.Sections)
{
Console.WriteLine("[" + section.SectionName + "]");
//Iterate through all the keys in the current section
//printing the values
foreach (KeyData key in section.Keys)
{
batch.Add(new AddConfig { ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0 });
// using (var service = HostContext.ResolveService<DeviceService>(authenticatedRequest))
//{
// service.Any(new AddConfig { ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0 });
//}
}
}
var client = new JsonServiceClient(TestingUrl);
var deviceConfigs = client.SendAll(batch.ToArray());
}
}
Firstly, you should never return value types in Services, your Request DTO says it returns a DeviceConfig Response Type DTO:
public class AddConfig : IReturn<DeviceConfig> { ... }
Which your Service should be returning instead.
I'm unclear how this can work or compile:
using (var service = HostContext.ResolveService<DeviceService>(authenticatedRequest))
{
service.SendAll(new AddConfig {
ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0
});
}
Since it's calling methods on the DeviceService Service class directly and there is no SendAll() method on the Service class (or in your example), were you using the Service Gateway instead?
I can't tell what the issue is from here without seeing the full source code and being able to repro the issue but it sounds like AddConfig is not recognized as a Service, is it appearing in the /metadata page? If not do you have it a class that inherits Service?
Otherwise if you can post a minimal repro on GitHub, I'll be able to identify the issue.
I am trying to implement service gateway pattern according to Service Gateway tutorial to support in-process handing via InProcessServiceGateway and external calling via JsonServiceClient in case ServiceStack service is deployed standalone. I use ServiceStack 4.5.8 version.
Validation feature works fine, but with InProcessServiceGateway, the failed validation throws ValidationException which directly results in a ServiceStack.FluentValidation.ValidationException in the client rather than populating ResponseStatus property of MyResponseDto. And I also tried GlobalRequestFilters and ServiceExceptionHandlers, both of them seem to work fine to capture ValidationException only with JsonHttpClient but InProcessServiceGateway.
Is there any way to make ValidationException thrown by InProcessServiceGateway captured and translated into Dto's ResponseStatus? Thanks.
My AppHost:
//Register CustomServiceGatewayFactory
container.Register<IServiceGatewayFactory>(x => new CustomServiceGatewayFactory()).ReusedWithin(ReuseScope.None);
//Validation feature
Plugins.Add(new ValidationFeature());
//Note: InProcessServiceGateway cannot reach here.
GlobalRequestFilters.Add((httpReq, httpRes, requestDto) =>
{
...
});
//Note: InProcessServiceGateway cannot reach here.
ServiceExceptionHandlers.Add((httpReq, request, ex) =>
{
...
});
My CustomServiceGatewayFactory:
public class CustomServiceGatewayFactory : ServiceGatewayFactoryBase
{
private IRequest _req;
public override IServiceGateway GetServiceGateway(IRequest request)
{
_req = request;
return base.GetServiceGateway(request);
}
public override IServiceGateway GetGateway(Type requestType)
{
var standaloneHosted = false;
var apiBaseUrl = string.Empty;
var apiSettings = _req.TryResolve<ApiSettings>();
if (apiSettings != null)
{
apiBaseUrl = apiSettings.ApiBaseUrl;
standaloneHosted = apiSettings.StandaloneHosted;
}
var gateway = !standaloneHosted
? (IServiceGateway)base.localGateway
: new JsonServiceClient(apiBaseUrl)
{
BearerToken = _req.GetBearerToken()
};
return gateway;
}
}
My client base controller (ASP.NET Web API):
public virtual IServiceGateway ApiGateway
{
get
{
var serviceGatewayFactory = HostContext.AppHost.TryResolve<IServiceGatewayFactory>();
var serviceGateway = serviceGatewayFactory.GetServiceGateway(HttpContext.Request.ToRequest());
return serviceGateway;
}
}
My client controller action (ASP.NET Web API):
var response = ApiGateway.Send<UpdateCustomerResponse>(new UpdateCustomer
{
CustomerGuid = customerGuid,
MobilePhoneNumber = mobilePhoneNumber
ValidationCode = validationCode
});
if (!response.Success)
{
return this.Error(response, response.ResponseStatus.Message);
}
My UpdateCustomer request DTO:
[Route("/customers/{CustomerGuid}", "PUT")]
public class UpdateCustomer : IPut, IReturn<UpdateCustomerResponse>
{
public Guid CustomerGuid { get; set; }
public string MobilePhoneNumber { get; set; }
public string ValidationCode { get; set; }
}
My UpdateCustomerValidator:
public class UpdateCustomerValidator : AbstractValidator<UpdateCustomer>
{
public UpdateCustomerValidator(ILocalizationService localizationService)
{
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.ValidationCode)
.NotEmpty()
.When(x => !string.IsNullOrWhiteSpace(x.MobilePhoneNumber))
.WithErrorCode(((int)ErrorCode.CUSTOMER_VALIDATIONCODE_EMPTY).ToString())
.WithMessage(ErrorCode.CUSTOMER_VALIDATIONCODE_EMPTY.GetLocalizedEnum(localizationService, Constants.LANGUAGE_ID));
}
}
My UpdateCustomerResponse DTO:
public class UpdateCustomerResponse
{
/// <summary>
/// Return true if successful; return false, if any error occurs.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Represents error details, populated only when any error occurs.
/// </summary>
public ResponseStatus ResponseStatus { get; set; }
}
ServiceStack 4.5.8's InProcessServiceGateway source code:
private TResponse ExecSync<TResponse>(object request)
{
foreach (var filter in HostContext.AppHost.GatewayRequestFilters)
{
filter(req, request);
if (req.Response.IsClosed)
return default(TResponse);
}
if (HostContext.HasPlugin<ValidationFeature>())
{
var validator = ValidatorCache.GetValidator(req, request.GetType());
if (validator != null)
{
var ruleSet = (string)(req.GetItem(Keywords.InvokeVerb) ?? req.Verb);
var result = validator.Validate(new ValidationContext(
request, null, new MultiRuleSetValidatorSelector(ruleSet)) {
Request = req
});
if (!result.IsValid)
throw new ValidationException(result.Errors);
}
}
var response = HostContext.ServiceController.Execute(request, req);
var responseTask = response as Task;
if (responseTask != null)
response = responseTask.GetResult();
return ConvertToResponse<TResponse>(response);
}
ServiceStack's Service Gateways now convert validation exceptions into WebServiceExceptions from this commit which is available from v4.5.13 that's now available on MyGet.
I have the following code to register Mapping (version 4.2)
public class ModelMapperProfile : Profile
{
protected override void Configure()
{
CreateMap<Case, CaseModel>();
CreateMap<CaseDetail, CaseDetailModel>();
}
}
public static class AutoMapperService
{
public static MapperConfiguration Initialize()
{
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ModelMapperProfile>();
});
return config;
}
}
And I register the dependency using unity as follows...
public static void RegisterTypes(IUnityContainer container)
{
container.LoadConfiguration();
var mapper = AutoMapperService.Initialize()
.CreateMapper();
container.RegisterInstance<IMapper>(mapper);
}
My here service constructor..
public TaxLiabilityCaseService(IMapper mapper,
IUnitOfWork unitofWork,
IRepository<Case> caseR,
IRepository<CaseDetail> caseDetailR)
{
_mapper = mapper;
_unitofWork = unitofWork;
_caseR = caseR;
_caseDetailR = caseDetailR;
}
And I get the following error message..
The current type, AutoMapper.IMapper, is an interface and cannot be
constructed. Are you missing a type mapping?
Answers found here did not work for me
What am I missing here
Try following these steps (MVC5):
Get Unity Nuget package:
Unity.Mvc5
Create this class:
public class MapperConfig
{
public static IMapper Mapper { get; set; }
public static void RegisterProfiles()
{
var config = new MapperConfiguration(cfg =>
{
// add profiles here
});
config.AssertConfigurationIsValid();
Mapper = config.CreateMapper();
}
}
In the UnityConfig file (created by the package), add this:
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterInstance<IMapper>(MapperConfig.Mapper);
}
In the Global.asax, add these:
protected void Application_Start()
{
MapperConfig.RegisterProfiles();
UnityConfig.RegisterComponents();
}
You should be good after this.
I have a self hosted service stack app in which I'm using Facebook Oath and Redis - the Facebook and redis side of things seem to be working ie. when I visit
abc.com/auth/facebook
The custom user session gets populated in OnAuthenticated method. The Redis cache has the data persisted correctly..so far so good
The problem Im having is understanding how to retrieve this CustomUserSession in a subsequent request. To begin with the oauth redirect page "/About-Us" is where I want to retrieve the session value however it is always null
[DataContract]
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string CustomId { get; set; }
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
{
// receiving session id here and can retrieve from redis cache
}
public override bool IsAuthorized(string provider)
{
// when using the [Authenticate] attribute - this Id is always
// a fresh value and so doesn't exist in cache and cannot be auth'd
string sessionKey = SessionFeature.GetSessionKey(this.Id);
cacheClient = ServiceStackHost.Instance.TryResolve<ICacheClient>();
CustomUserSession session = cacheClient.Get<CustomUserSession>(sessionKey);
if (session == null)
{
return false;
}
return session.IsAuthenticated;
}
}
[DefaultView("AboutUs")]
public class AboutUsService : AppServiceBase
{
public object Get(AboutUsRequest request)
{
var sess = base.UserSession;
return new AboutUsResponse
{
//custom Id is always null??
Name = sess.CustomId
};
}
}
public abstract class AppServiceBase : Service
{
protected CustomUserSession UserSession
{
get
{
return base.SessionAs<CustomUserSession>();
}
}
}
How I register the cache & session etc.
AppConfig = new AppConfig(appSettings);
container.Register(AppConfig);
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("10.1.1.10:6379"));
container.Register(c =>
c.Resolve<IRedisClientsManager>().GetCacheClient());
ConfigureAuth(container, appSettings);
the contents of ConfigureAuth()
var authFeature = new AuthFeature(
() => new CustomUserSession(),
new IAuthProvider[]
{
new FacebookAuthProvider(appSettings), // override of BasicAuthProvider
}
) {HtmlRedirect = null, IncludeAssignRoleServices = false};
Plugins.Add(authFeature);
I feel I'm missing something obvious here.... thanks in advance
To register to use a CustomUserSession for your typed Sessions, it needs to be specified when you register the AuthFeature, e.g:
Plugins.Add(new AuthFeature(() => new CustomUserSession(), ...));