servicestackservicestack-razor

Service Stack Razor View Not Found


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;
        }
    }
}

Solution

  • 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.