I have started building a controllerless MVC app built on top of Service Stack making use of 'RazorFormat'. I have built a couple of pages successfully. These pages simply map to a service that return a simple single Document object. This is all fine. I have built another view that should be returned when I access a specific route. This service returns an IEnumerable. I am able to step through the code to see that my service is returning correctly but the view never renders, I see a NullReferenceException. During debugging I am able to see that the operation name that throws is ResourceTemplateRequest and the stack trace:
Error CodeNullReferenceException MessageObject reference not set to an
instance of an object. Stack Trace at ASP.___Layout.Execute() at
ServiceStack.Razor.ViewPage1.WriteTo(StreamWriter writer) at
ServiceStack.Razor.Managers.RazorPageResolver.ExecuteRazorPageWithLayout(IHttpRequest
httpReq, IHttpResponse httpRes, Object model, IRazorView page, Func1
layout) at
ServiceStack.Razor.Managers.RazorPageResolver.ExecuteRazorPageWithLayout(IHttpRequest
httpReq, IHttpResponse httpRes, Object model, IRazorView page, Func1
layout) at
ServiceStack.Razor.Managers.RazorPageResolver.ResolveAndExecuteRazorPage(IHttpRequest
httpReq, IHttpResponse httpRes, Object model, RazorPage razorPage) at
ServiceStack.Razor.Managers.RazorPageResolver.ProcessRequest(IHttpRequest
httpReq, IHttpResponse httpRes, Object dto) at
ServiceStack.WebHost.Endpoints.Formats.HtmlFormat.<>c__DisplayClass1.<SerializeToStream>b__0(IViewEngine
x) at System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func`2
predicate) at
ServiceStack.WebHost.Endpoints.Formats.HtmlFormat.SerializeToStream(IRequestContext
requestContext, Object response, IHttpResponse httpRes) at
ServiceStack.WebHost.Endpoints.Extensions.HttpResponseExtensions.WriteToResponse(IHttpResponse
response, Object result, ResponseSerializerDelegate defaultAction,
IRequestContext serializerCtx, Byte[] bodyPrefix, Byte[] bodySuffix)
but I cannot figure out how to fix it. My service is decorated with the [DefaultView("ResourceTemplate")] attribute. Any ideas?
Even when I comment everything out in my view I get the exception. Here is some code
Service
[Authenticate]
public class ResourceTemplateService : Service
{
[DefaultView("ResourceTemplate")]
public ResourceTemplateResponse Get(ResourceTemplateRequest request)
{
var filter = factory.SetUp()
.WithTopN(Paging.GetTopN(request))
.WithGlobalPath(request.NodeAliasPath.StripChannel())
.Value;
var documents = repository.GetDocumentsByPath(filter).ToList();
var currentDocument = repository.GetDocumentByNodeAliasPath(request.NodeAliasPath);
var page = Paging.GetPage(documents, request);
if (documents.None())
throw HttpError.NotFound(string.Format("No children found for path {0}", request.NodeAliasPath));
return new ResourceTemplateResponse
{
Count = documents.Count(),
CurrentDocument = currentDocument,
Documents = page,
CurrentNodeAliasPath = request.NodeAliasPath
};
}
}
Request
[Route("/resource/{NodeAliasPath*}")]
public class ResourceTemplateRequest : DocumentCollectionRequest, IReturn<ResourceTemplateResponse>
{
public ResourceTemplateRequest()
{
View = "ResourceTemplate";
//Set base call properties
}
}
View
#using Zoo.Optus.YesCentral.Mobile.Model
#using Zoo.Optus.YesCentral.Web.Factories
#inherits ServiceStack.Razor.ViewPage<ResourceTemplateResponse>
#{
Layout = "_Layout";
ActivityFactory.LogPageVisit(Model.CurrentDocument.DocumentName, Model.CurrentDocument.NodeId, true);
}
#foreach (var document in #Model.Documents)
{
#document.DocumentName.ToMvcHtmlString()
}
Related
I need to run my ASP.NET MVC application in STA mode. For this purpose I developed some custom classes based on Programming ASP.NET MVC 4: Developing Real-World Web Applications with ASP.NET MVC. Here they are:
public class StaThreadRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
if (requestContext == null)
throw new ArgumentNullException("requestContext");
return new StaThreadHttpAsyncHandler(requestContext);
}
}
public class StaThreadHttpAsyncHandler : Page, IHttpAsyncHandler, IRequiresSessionState
{
private readonly RequestContext _requestContext;
public StaThreadHttpAsyncHandler(RequestContext requestContext)
{
if (requestContext == null)
throw new ArgumentNullException("requestContext");
_requestContext = requestContext;
}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
return this.AspCompatBeginProcessRequest(context, cb, extraData);
}
protected override void OnInit(EventArgs e)
{
var controllerName = _requestContext.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = controllerFactory.CreateController(_requestContext, controllerName);
if (controller == null)
throw new InvalidOperationException("Could not find controller: " + controllerName);
try
{
controller.Execute(_requestContext);
}
finally
{
controllerFactory.ReleaseController(controller);
}
this.Context.ApplicationInstance.CompleteRequest();
}
public void EndProcessRequest(IAsyncResult result)
{
this.AspCompatEndProcessRequest(result);
}
public override void ProcessRequest(HttpContext httpContext)
{
throw new NotSupportedException("STAThreadRouteHandler does not support ProcessRequest called (only BeginProcessRequest)");
}
}
Also adjusted RouteConfig:
routes.Add(new Route("{controller}/{action}/{id}", new StaThreadRouteHandler ()))
Everything works fine for majority of my actions, but I have two that take somewhere between 10 and 20 seconds to finish. For these two actions, method EndProcessRequest is never executed, even though no exception is thrown during their execution and this.Context.ApplicationInstance.CompleteRequest(); is called within OnInit without problems. As a result, such request ends in IIS with state ExecuteRequestHandler and is stuck there forever and also blocks new requests from being served.
Why is this happening? Why breakpoint in EndProcessRequest is never hit for long running actions, while it works fine for shorter actions (1-5 seconds).
Problem was not related to execution time of particular actions at all. Not really sure what was happening under hood but all the problematic actions had one in common - they were creating one particular COM object, which I guess was somehow not properly destroyed and as a result EndProcessRequest was not called.
The issue was happening for any .NET COM object, native COM objects are fine and EndProcessRequest is called.
All,
I am trying to modify the payload of incoming object via the web API. Currently I'm using a custom formatter which inherits from JsonMediaTypeFormatter and overrides the relevant methods.
Looks like this:
public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger,
CancellationToken cancellationToken)
{
object obj = await base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);
TrySetEventNo(obj, GetEventNo());
return obj;
}
private void TrySetEventNo(object content, long eventNo)
{
if (content is EventModelBase)
{
EventModelBase eventBase = (EventModelBase)content;
eventBase.EventNo = eventNo;
}
}
I'm using this to track every object that comes through the API.
Before all of this happens, I have a MessageHandler which is creating an event number and adding it to Request.Properties.
Trying to get the event number in the formatter which was created previously in the MessageHandler is proving difficult. Access HttpContext.Current.Items["MS_HttpRequestMessage"].Properties seems to be a different request as it does not contain the event number.
I've two questions:
Am I doing this the correctly or is there a better way?
If I am taking the correct approach, how to I get the correct Request to extract the event number?
Thanks
I've found a solution, instead of attempting to do this inside a formatter I'm now using an ActionFilterAttribute.
overriding OnActionExecuting(HttpActionContext actionContext) and enumerating action actionContext.ActionArguments.
The complete solution looks like so:
public class SetModelEventNumberActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
foreach (KeyValuePair<string, object> actionArgument in actionContext.ActionArguments)
{
TrySetEventNo(actionArgument.Value, GetEventNo(actionContext));
}
base.OnActionExecuting(actionContext);
}
private void TrySetEventNo(object content, long eventNo)
{
if (content is EventPivotRequestMessageBase)
{
EventPivotRequestMessageBase eventBase = (EventPivotRequestMessageBase)content;
eventBase.EventNo = eventNo;
}
}
private long GetEventNo(HttpActionContext actionContext)
{
long eventNo = (long)actionContext.Request.Properties[Constant.EVENT_ID];
return eventNo;
}
}
I have the following code:
public class Program : NancyModule
{
static void Main(string[] args)
{
using (var host = new NancyHost(new Uri("http://localhost:444"), new CustomConventionsBootstrapper()))
{
host.Start();
Console.ReadLine();
}
}
public Program()
{
Get["/"] = parameter =>
{
dynamic var = new ExpandoObject();
var.Test = "Lol";
return View["RazorView.cshtml", var];
};
}
}
public class CustomConventionsBootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
base.ConfigureApplicationContainer(container);
//This should be the assembly your views are embedded in
var assembly = Assembly.GetEntryAssembly();
ResourceViewLocationProvider.RootNamespaces.Add(assembly, "NancyTest.Views");
}
protected override NancyInternalConfiguration InternalConfiguration
{
get
{
var res = base.InternalConfiguration;
res.ViewLocationProvider = typeof(ResourceViewLocationProvider);
return res;
}
}
void OnConfigurationBuilder(NancyInternalConfiguration x)
{
x.ViewLocationProvider = typeof(ResourceViewLocationProvider);
}
}
And I have RazorView.cshtml in a folder Views in my project set as embedded resource, however each time I open the page it will give me
Nancy.RequestExecutionException: Oh noes! ---> Nancy.ViewEngines.ViewNotFoundException: Unable to locate view 'RazorView.cshtml'
Currently available view engine extensions: sshtml,html,htm,cshtml,vbhtml
Locations inspected: views/Program/RazorView.cshtml-en-GB,views/Program/RazorView.cshtml,Program/RazorView.cshtml-en-GB,Program/RazorView.cshtml,views/RazorView.cshtml-en-GB,views/RazorView.cshtml,RazorView.cshtml-en-GB,RazorView.cshtml
Root path: C:\Users\Student\documents\visual studio 2013\Projects\NancyTest\NancyTest\bin\Debug
If you were expecting raw data back, make sure you set the 'Accept'-header of the request to correct format, for example 'application/json'
bij Nancy.ViewEngines.DefaultViewFactory.GetRenderedView(String viewName, Object model, ViewLocationContext viewLocationContext)
bij Nancy.ViewEngines.DefaultViewFactory.RenderView(String viewName, Object model, ViewLocationContext viewLocationContext)
bij Nancy.Responses.Negotiation.ViewProcessor.Process(MediaRange requestedMediaRange, Object model, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.NegotiateResponse(IEnumerable`1 compatibleHeaders, Object model, Negotiator negotiator, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.ProcessAsNegotiator(Object routeResult, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.InvokeRouteWithStrategy(Object result, NancyContext context)
bij Nancy.Routing.DefaultRouteInvoker.<>c__DisplayClass9.b__5(Task`1 completedTask)
--- Einde van intern uitzonderingsstackpad ---
bij Nancy.NancyEngine.InvokeOnErrorHook(NancyContext context, ErrorPipeline pipeline, Exception ex)
It is worth noting that this only happens when using Razor views. When I use a simple html file it can find those files fine. I even tried to run old example projects found online on the current (0.22.2) Nancy version, but no luck there either. What's going on?
Thanks
Okay figured this out, but I have to say this is kinda stupid. Checked the source, this is the culprit:
ResourceAssemblyProvider.cs line 31
private static IEnumerable<Assembly> GetFilteredAssemblies()
{
return AppDomainAssemblyTypeScanner.Assemblies
.Where(x => !x.IsDynamic)
.Where(x => !x.GetName().Name.StartsWith("Nancy", StringComparison.OrdinalIgnoreCase));
}
My assembly is called 'NancyTest'. Changed to 'TestNancy', works now. I suggest finding a better way of excluding your own assemblies.
Also, when working with embedded views, one can easily forget to add the view as an embedded resource (a common gotcha that took me a while to get used to):
Right click on view-file -> properties -> Build Action -> select "Embedded Resource"
I am writing a media type formatter for HTML to automatically generate a Razor view based on an html request from the user. I am doing this for use inside a SelfHosted service. I need to detect what controller/action was requested to allow me to pick the view to render into.
public class RazorHtmlMediaTypeFormatter : MediaTypeFormatter
{
public RazorHtmlMediaTypeFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
public override bool CanWriteType(Type type)
{
return true;
}
public override bool CanReadType(Type type)
{
return false;
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, System.Net.TransportContext transportContext)
{
return Task.Factory.StartNew(() =>
{
var view = Razor.Resolve(String.Format("{0}.{1}.cshtml", something.Controller, something.Action), value);
byte[] buf = System.Text.Encoding.Default.GetBytes(view.Run(new ExecuteContext()));
stream.Write(buf, 0, buf.Length);
stream.Flush();
});
}
}
Why not wrapping your returned objects in Metadata<T>?
I.e. return, instead of MyCustomObject, Metadata<MyCustomObject>. As Metadata properties, you can set controller name and action. Then in the formatter, just decouple the Metadata and your custom object, and serialize just that custom object.
I blogged about this approach here - http://www.strathweb.com/2012/06/extending-your-asp-net-web-api-responses-with-useful-metadata/. While the purpose of the article is a bit different, I am sure you can relate it to your needs.
Edit: or if you are OK with a small hack, use a custom filter and headers:
public override void OnActionExecuting(HttpActionContext actionContext)
{
actionContext.Response.Headers.Add("controller", actionContext.ActionDescriptor.ControllerDescriptor.ControllerName);
actionContext.Response.Headers.Add("action", actionContext.ActionDescriptor.ActionName;);
base.OnActionExecuting(actionContext);
}
then just read it from the headers in the formatter, and remove the header entries so that they don't get sent to the client.
Web API Contrib has a working RazorViewFormatter in here.
Can I do something like this?
[HttpPost]
public ActionResult Index(WizardViewModel wizard, IStepViewModel step)
{
Where I have the following in my global.asax.cs application_start
ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());
ModelBinders.Binders.Add(typeof(WizardViewModel), new WizardViewModelBinder());
Update
So, I tried to see what is wrong. Here is my new code. It seems that the problem is with this WizardViewModel and it's binder. What "tells" the application to expect and incoming Wizard model?
[HttpPost]
public ActionResult Index(WizardViewModel wizard)
{
Where I have the following in my global.asax.cs application_start
ModelBinders.Binders.Add(typeof(WizardViewModel), new WizardViewModelBinder());
Complete Binder Code
namespace Tangible.Binders
{
public class StepViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var stepTypeValue = bindingContext.ValueProvider.GetValue("StepType");
var stepType = Type.GetType((string)stepTypeValue.ConvertTo(typeof(string)), true);
var step = Activator.CreateInstance(stepType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => step, stepType);
return step;
}
}
public class WizardViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var wizardValue = bindingContext.ValueProvider.GetValue("wizard");
if (wizardValue != null)
{
var wizardType = Type.GetType((string)wizardValue.ConvertTo(typeof(string)), true);
var wizard = Activator.CreateInstance(wizardType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => wizard, wizardType);
return wizard;
}
else
{
var wizard = new Tangible.Models.WizardViewModel();
wizard.Initialize();
return wizard;
}
}
}
}
The answer is simple - Yes! That is what you SHOULD do when you've got custom logic for binding values to your parameters. You can even do that with the use of ModelBinderAttribute, set on each of parameters individually.
[HttpPost]
public ActionResult Index([ModelBinder(typeof(WizardViewModelBinder))]WizardViewModel wizard,
[ModelBinder(typeof(StepViewModelBinder))]IStepViewModel step)
{ }
And as I see, the mistake is in your model binder code. I don't have time to check it up, but as far as i remember, CreateModel is used by model binder to create instance of the model, and then that returned instance is model-binded. So, override BindModel instead of CreateModel and write your model binding logic in BindModel. That definitely works.
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//your model binding logic here
}
I have done something similar in the past where I passed a string and then split the value.
I would say the answer is just: Yes! In your comment you are concerned about "many other issues" which might cause trouble. Without knowing what issues you have in mind, it's hard to help you. But what you have done is exactly what model binders are designed for. And there is no reason, why you should have only one object per action.
I was really disenchanted with the hoops that ASP.NET MVC Model Binding required me to jump through to get some basic deserialization.
Since model binding was no where near as transparent as I had hoped for complex Model / ViewModels I simply built a custom ActionFilter to resolve the types [and ONLY the types] I want to deserialize in the action method and use ServiceStack.Text for all my serialization / deserialization needs.
Take a look here:
https://gist.github.com/3b18a58922fdd8d5a963