Viewcomponent alternative for ajax refresh - ajax

I have a viewcomponent that contains some reusable business logic that embed in various pages. This has been working fine. However, I now have a requirement to refresh the viewcomponent using ajax.
Is there any way to accomplish this? From what I have read, it is not possible, although that info was a bit outdated.
If it is not possible, what is the best alternative?

On beta7 it is now possible to return a ViewComponent directly from a controller. Check the MVC/Razor section of the announcement
The new ViewComponentResult in MVC makes it easy to return the result
of a ViewComponent from an action. This allows you to easily expose
the logic of a ViewComponent as a standalone endpoint.
So you could have a simple view component like this:
[ViewComponent(Name = "MyViewComponent")]
public class MyViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var time = DateTime.Now.ToString("h:mm:ss");
return Content($"The current time is {time}");
}
}
Create a method in a controller like:
public IActionResult MyViewComponent()
{
return ViewComponent("MyViewComponent");
}
And do a better job than my quick and dirty ajax refresh:
var container = $("#myComponentContainer");
var refreshComponent = function () {
$.get("/Home/MyViewComponent", function (data) { container.html(data); });
};
$(function () { window.setInterval(refreshComponent, 1000); });
Of course, prior to beta7 you could create a view as the workaround suggested by #eedam or use the approach described in these answers

Related

How to restrict a webapi controller method invokation using a filter

I have several controllers, and each of them has operations that represents
REST endpoints.
i want to invoke those operations based on conditions
can it be possible to achieve it using filters with attributes and not using (if..else) statements in all operations?
if so, can you please provide a skeleton of how doing it?
thanks.
If it is asp.net core then check please this link https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2
You can do something like this
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
if (...)
{
// do something before the action executes
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
}

Angularjs caches ajax data into a service

I have such a simple scenario,
App starts from Main View (/main), then click top right button to Sub View (/sub).
During app launching app.run(), user's profile will be loaded into a service userService, once if user went to Sub View, this profile will be read from that service userService then display, here is the code,
app.run(function($http, $rootScope, userService){
$http.get('/profile').then(function(result){
userService.setProfile(result.data.profile);
});
});
app.service('userService', function(){
var user = {}
this.setProfile(profile){
user.profile = profile;
};
this.getProfile(){
return user.profile;
}
});
In Sub View, getProfile() was invoked to display the info.
It works if user start from Main View -> button -> Sub View, however, if user manually refreshed Sub View or just start from Sub View, getProfile() will get nothing to display,I know that's because before the promise of getting profile returned, Sub View had been proceed.
I don't like to read profile from Sub View directly and dynamically because I have other pages need profile info as well, so is there any workaround or better design? thanks.
Instead of using app.run you should probably utilize your route provider for this. Whether you use ngRoute or ui-router they both have resolve functionality. Instead of getting your profile in app.run you should probably move that to userService as well.
app.service('userService', function(){
var self = this;
self.user = {};
self.getProfile = function() {
return self.user.profile;
};
self.init = function() {
return $http.get('/profile').then(function(result){
self.user.profile = result.data.profile;
});
};
});
Now that your service is more factory like, you can utilize the initialization of it in the route provider. I use ui-router but this can easily be applied to ngRoute as well.
I start by creating an abstract state that handles the resolve which I can than 'import' in whichever other states I need.
.state('init', {
abstract: true,
resolve: ['userService', function(userService) {
return userService.init();
}]
});
Now I just use it in other states and I can assure that the userService is initialized.
.state('subView', {
parent: 'init',
url: '/subView'
//other state stuff like template, controller, etc
});
The way I've worked around that is add the relevant data to $window.sessionStorage, roughly like this (you'll need to make $window available):
app.service('userService', function(){
var user = {}
if ($window.sessionStorage.profile)
this.user.profile = JSON.parse($window.sessionStorage.profile)
this.setProfile(profile){
user.profile = profile;
this.$window.sessionStorage.profile = JSON.stringify(profile)
};
this.getProfile(){
return user.profile;
}
});

Restricting auto Help Page contents when using Attribute Routing in Web API 2

I'm currently implementing a Web API using Web API 2's attribute routing (http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2). I am also using the Help Pages module in order to automatically generate documentation from XML comments (http://www.asp.net/web-api/overview/creating-web-apis/creating-api-help-pages).
For this API I am providing support for optional return format extensions, so that every API method has a pair of routes defined on it like so:
[HttpGet]
[Route("Path/Foo")]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFoo()
{
// Some API function.
}
This allows a user to hit any of these and get a result:
www.example.com/api/Controller/Path/Foo
www.example.com/api/Controller/Path/Foo.json
www.example.com/api/Controller/Path/Foo.xml
My issue is that when Help Pages uses MapHttpAttributeRoutes() to generate documentation, it is picking up both routes for each method. So right now I see help for:
api/Controller/Foo
api/Controller/Foo.{ext}
But I want to only see:
api/Controller/Foo.{ext}
I would prefer to hide the non-extension route on each method, so that every method only shows a single Help Page entry.
Has anyone else tried something similar? Is there a work around that I am missing?
My question would be is that, would consumers of your api figure out easily that the {ext} is optional?...personally, I would prefer the default behavior...but anyways following are some workarounds that I can think of:
A quick and dirty workaround. Split the DoFoo into 2 actions like DoFoo() and DoFooWithExt maybe. Notice that I am using an attribute called ApiExplorerSettings, which is for HelpPage purposes. Example below:
[HttpGet]
[Route("Path/Foo")]
[ApiExplorerSettings(IgnoreApi=true)]
public HttpResponseMessage DoFoo()
{
return DoFooHelper();
}
[HttpGet]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFooWithExt()
{
return DoFooHelper();
}
private HttpResponseMessage DoFooHelper()
{
//do something
}
Create a custom ApiExplorer (which HelpPage feature uses internally) and check for specific routes like the following and can decide whether to show the action or not for that particular route.
// update the config with this custom implementation
config.Services.Replace(typeof(IApiExplorer), new CustomApiExplorer(config));
public class CustomApiExplorer : ApiExplorer
{
public CustomApiExplorer(HttpConfiguration config) : base(config)
{
}
public override bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route)
{
if (route.RouteTemplate.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return base.ShouldExploreAction(actionVariableValue, actionDescriptor, route);
}
}
Get list of all ApiDescription from the default ApiExplorer and then filter out the descriptions which you do not like. Example:
Configuration.Services.GetApiExplorer().ApiDescriptions.Where((apiDesc) => !apiDesc.RelativePath.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))

HttpRoutes - how do they work?

I´m struggling with URLs for ajax-reader/JSON. Each time I think I understand it, it seems that I haven´t.
Please, can anybody explain the logic behind this???
I got this Controller:
public class ServiceController : DnnApiController
{
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetAllItems(int moduleId)
{
MyProjectController controller = new MyProjectController();
IEnumerable<ItemInfo> items = controller.GetAllItems(moduleId);
return Request.CreateResponse(HttpStatusCode.OK, items);
}
}
I got this Routemapper:
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("MyProject",
"default",
"{controller}/{action}",
new[] { "MyCompany.MyProject.Services" });
}
}
At what URL can I read the data with $.ajax() and what is the URL showing me the data in a browser?
Thanx in Advance!
Asle :)
This is how I do it (Note: this will only work with DNN6.2 and above);
In the View.ascx.cs add
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
ServicesFramework.Instance.RequestAjaxScriptSupport();
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
jQuery.RequestDnnPluginsRegistration();
}
This ensures that jquery and the required DNN ajax plugins are added.
Initiate the services framework jquery plugin in the View.ascx like this inside javascript script tags (S.O. wouldn't allow me to include them)
var modId = <%=ModuleId %>;
var sf = $.ServicesFramework(modId);
Now in a separate javascript file or in the view.ascx control add the ajax function
function getAllItems(){
$.ajax({
type:"GET",
url:sf.getServiceRoot("MyProject")+"Service/GetAllItems",
beforeSend:sf.setModuleHeaders,
data:{moduleId:modId},
cache:false
}).done(function(data){
alert("Success!");
}).fail(function(){
alert("Crashed!");
}).always(function(){
//something you want done whether passed or failed
//like hide progress bar, ajax spinner etc.
});
}
The DNN jquery plugin will build the url which will look similar to this (Note: 142 is just for illustration purpose and will be replace with actual module id)
/DesktopModules/MyProject/API/Service/GetAllItems?moduleId=142
The URL will be something like
/desktopmodules/SlidePresentation/API/SlidePresetnation.ashx/ListOfSlides
I have examples at
https://slidepresentation.codeplex.com/SourceControl/latest
but they were for DNN6, they might require a few updates due to the API changes for DNN 7
you can see a DNN7 module that has a service layer at https://dnnsimplearticle.codeplex.com/SourceControl/latest#cs/services/

Using only a controller in FW1 without a view

I have an Ajax request that sends some data to a page and expects back a truthy or falsey value depending on if the data was saved. In my controller I do everything and set the content to a true or false value. I really don't want to create a view just to output 1 variable, so I was wondering if there was a way that I don't have to use a view and only use the controller to output simple strings.
I believe you cannot disable views completely, but there's a pretty simple workaround: you can create one view and use it for many actions.
Let's say we've created the view views/main/ajax.cfm, what could be inside it? Obviously, simplest way is:
<cfoutput>#HTMLEditFormat(rc.response)#</cfoutput>
Personally I like returning JSON, it allows me to have status field, plus data, if needed. This way my view looks like this:
<cfheader name="Content-Type" value="application/json" />
<cfoutput>#SerializeJSON(rc.response)#</cfoutput>
Any way, now in our action we need to do something like this:
// prevent displaying the layout
request.layout = false;
// force special view
variables.fw.setView("main.ajax");
// init response (according to the choice made earlier)
rc.response["status"] = "OK";
rc.response = "";
There's one more gotcha for this. Sometimes you don't want AJAX page to be accessed directly (like opened in browser), or vise-versa -- want to do some debugging when it is.
There's a cool helper isAjax in CFWheels framework, it is easy to port to the FW/1. It could be as simple as adding method like this to controller:
/*
* Check if request is performed via AJAX
*/
private boolean function isAjax() {
return (cgi.HTTP_X_REQUESTED_WITH EQ "XMLHTTPRequest");
}
Actually, that setup code above is also helper method in my apps:
/*
* Set up for AJAX response
*/
private struct function setAjax() {
// prevent displaying the layout
request.layout = false;
// force special view
variables.fw.setView("main.ajax");
local.response["status"] = "OK";
return local.response;
}
So in my action code whole check looks like this, which is pretty compact and convenient:
if (isAjax()) {
rc.response = setAjax();
}
else {
return showNotFound();
}
Hope this helps.
You can't output directly from a Controller: its job is just to call the Model and pass data to the View, so you'll need a view template to do the outputting.
However, you can avoid having to create a separate view for each controller method by using the framework's setView() method. This allows you to override the convention and apply a single view to multiple controller methods. So you could set up a generic "ajax view" and then use it to output the data from any of your controllers:
views/main/ajax.cfm
<!---Prevent any layouts from being applied--->
<cfset request.layout=false>
<!--- Minimise white space by resetting the output buffer and only returning the following cfoutput --->
<cfcontent type="text/html; charset=utf-8" reset="yes"><cfoutput>#rc.result#</cfoutput>
controller.cfc
function init( fw )
{
variables.fw=arguments.fw;
return this;
}
function getAjaxResponse( rc )
{
rc.result=1;
fw.setView( "main.ajax" );
}
function getAnotherAjaxResponse( rc )
{
rc.result=0;
fw.setView( "main.ajax" );
}
You can use onMissingView in you Application.cfc to handle the response for ajax calls, this way you don't need to perform any extra logic in your controller methods.
// Application.cfc
function onMissingView(rc) {
if(structKeyExists(rc, "ajaxdata") && isAjaxRequest()) {
request.layout = false;
content type="application/json";
return serializeJSON(rc.ajaxdata);
}
else {
return view("main/notfound");
}
}
function isAjaxRequest() {
var headers = getHttpRequestData().headers;
return structKeyExists(headers, "X-Requested-With")
&& (headers["X-Requested-With"] eq "XMLHttpRequest");
}
// controller cfc
function dosomething(rc) {
rc.ajaxdata = getSomeService().doSomething();
}
This checks if the request context has an ajaxdata key, and is a genuine ajax request, then returns the serialize data. If it doesn't then it renders the main.notfound view

Resources