Get ActionResult of another controller-action?

2019-05-11 14:02发布

I want to store items in the application cache so it's lazy loaded from the main layout view.

I also want to be able to invalidate the cache, so if it's invalid, next time when the items-collection is requested, it's reloaded to that cache location.

Here's what I've implemented:

In the controller:

protected IEnumerable<Slide> CachedSlides
{
  get { return HttpContext.Application[SlidesCacheKey] as IEnumerable<Slide>; }
  set { HttpContext.Application[SlidesCacheKey] = value; }
}

private void ClearSlides()
{
  CachedSlides = null;
}

[AllowAnonymous]
public IEnumerable<Slider> GetSlides()
{
  if (CachedSlides == null)
    CachedSlides = Context.Slides.OrderBy(p => p.SortOrder).ToArray();
  return CachedSlides;
}

I the view (better said in 'a' view, I want to be able to load it from every view):

@{
  var sliderController = new LevEl.Controllers.Admin.SliderController().  
  var sliderModel = sliderController.GetSlides();
}

It throws an exception because when I initialize the controller in the view, the HttpContext property returns null (which leads to a NullReferenceException).

Any other ways to implement this will also be welcome.

2条回答
家丑人穷心不美
2楼-- · 2019-05-11 14:18

You could define your GetSlides method in a BaseController:

public class BaseController : Controller
{
    protected IEnumerable<Slide> CachedSlides
    {
        get { return HttpContext.Application[SlidesCacheKey] as IEnumerable<Slide>; }
        set { HttpContext.Application[SlidesCacheKey] = value; }
    }

    private void ClearSlides()
    {
        CachedSlides = null;
    }

    public IEnumerable<Slide> GetSlides()
    {
        if (CachedSlides == null)
            CachedSlides = Context.Slides.OrderBy(p => p.SortOrder).ToArray();
        return CachedSlides;
    }
}

So all of your controllers have to inherit from BaseController.

In the view you could do this:

@{
    var baseController = (BaseController)ViewContext.Controller;
    var slides = baseController.GetSlides();
}
查看更多
孤傲高冷的网名
3楼-- · 2019-05-11 14:29

First of all, you may want to consider using the Cache instead of the Application dictionary, specially since your cached data will expire at some point. Take a look at this question

Also, consider if the controller method will just be used by the views because MVC will expose all public methods in the controller. If you don´t want this method to be freely accessed using an URL then set the [NonAction] attribute.

Regarding your error, a quick way to fix it will be accessing the Application object through System.Web.HttpContext.Current in the implementation of the CachedSlides property of the controller.

You could also set the ControllerContext when creating a new instance of the SliderController in the view. That way, the HttpContext in the controller will not return null when accessing the CachedSlides property:

@{
    var sliderController = new LevEl.Controllers.Admin.SliderController();
    sliderController.ControllerContext = new ControllerContext(ViewContext.RequestContext, sliderController);
    var sliderModel = sliderController.GetSlides();
}

If it makes sense for you to have a base controller class that will handle all of these views, then getting the controller will be cleaner. You would just need to cast the ViewContext.Controller instance as that base controller class:

var sliderController = ViewContext.Controller as BaseSlideController;
var sliderModel = sliderController.GetSlides();

However all of those approaches will require you to add that piece of code to every view. You may consider having a base class for all of your views that will require accessing the Slide collection:

public abstract class SlidesEnabledView<T> : WebViewPage<T>
{
    private IEnumerable<Slide> _slides;
    protected IEnumerable<Slide> Slides
    {
        get
        {
            if(_slides == null)
            {
                var sliderController = ViewContext.Controller as BaseSlideController;
                _slides = sliderController.GetSlides();
            }
            return _slides;
        }
    }
}

You will then add the @inherits tag to your views, so they inherit from the base class we have just created (When the @inherits is used, you cannot also use the @model, so in the @inherits you will bound the generic base view type ti a concrete model type). This will allow you to use the property defined in the base SlidesEnabledView view class. Assuming the namespace for SlidesEnabledView is Level.ViewClasses.Admin, this would look like:

@inherits Level.ViewClasses.Admin.SlidesEnabledView<SomeViewModelClass> 

Number of Slides: @Slides.Count() 

Finally, if you are using DI through your site and you have configured a DependencyResolver, then you could consider moving the logic for getting the slides into its own class and interface like ISlidesProvider and CachedSlideProvider. You could then use property injection in the abstract view class for getting an instance of ISlidesProvider that will be used in the Slides property:

public interface ISlidesProvider
{ 
    IEnumerable<Slide> GetSlides();
}

public class CachedSlideProvider : ISlidesProvider
{
    //you will need a constructor that takes the "Context" object, which will be injected into this class

    public IEnumerable<Slide> GetSlides()
    {
        if (CachedSlides == null)
            CachedSlides = Context.Slides.OrderBy(p => p.SortOrder).ToArray();
        return CachedSlides;
    }

    private IEnumerable<Slide> CachedSlides
    {
        get { return System.Web.HttpRuntime.Cache[SlidesCacheKey] as IEnumerable<Slide>; }
        set { System.Web.HttpRuntime.Cache[SlidesCacheKey] = value; }
    }
}

public abstract class SlidesEnabledView<T> : WebViewPage<T>
{
    private IEnumerable<Slide> _slides;
    protected IEnumerable<Slide> Slides
    {
        get
        {
            if(_slides == null)
            {
                _slides = this.SlidesProvider.GetSlides();
            }
            return _slides;
        }
    }

    //This property will be set by your DI container
    //You have configured ISlidesProvider to be resolved as CachedSlidesProvider in the DI container
    //You have also implemented and registered an MVC DependencyResolver that uses your DI container
    //For example, using Microsoft Unity you could set this attribute
    [Dependency]
    public ISlidesProvider SlidesProvider { get; set; }        
}

As you were creating an instance of the controller using a parameterless constructor, probably you are not using DI on your site. So this DI option may be a bit of overkill just for resolving this particular problem.

查看更多
登录 后发表回答