I've been doing some searching and can't quite find what I'm after.
I'm working on an Umbraco project and need to render the contents of a specific page inside a parent page.
I tried
#Html.Action("MyAction", "MyController")
but this caused the assigned content item of my page to be null (because I've gone to the action rather than going to the umbraco url) which isn't really a big surprise.
Is there a Html method like Action that accepts a url instead of an action name and controller name?
I'm after something like this
#Html.LoadUrl("~/my-controller-section/my-action-page")
I could write an extension myself to do it, but was really hoping that there would already be something built into MVC to do this. I don't want to re-invent the wheel.
I'm using ASP.Net MVC 4.
OK, so it seems that there isn't a way to do this in MVC so I've reluctantly rolled my own. If a built in way of doing this comes to light I'll swap it out.
I've implemented the LoadUrl method that I proposed in my question, it's consumed like this:
#Html.LoadUrl("http://foo/bar")
The implementation looks like this (sorry it's a bit long, but I wanted to include a full working example so you can just copy and paste it).
You'll notice that I set the authentication cookie on the request, otherwise it'll just return the login page instead of the requested page. I've specifically implemented this for Forms Authentication. This part isn't required if your site doesn't use authentication.
public static class MvcExtensions
{
/// <summary>
/// Invokes a request to the specified url and returns the result as an HTML string.
/// </summary>
/// <param name="thisHtml">The HTML helper instance that this method extends.</param>
/// <param name="url">The url to invoke the request on.</param>
/// <returns>The url contents as an HTML string.</returns>
public static MvcHtmlString LoadUrl(this HtmlHelper thisHtml, string url)
{
//get the url as an absolute url
UrlHelper helper = GetUrlHelper();
url = helper.Absolute(url);
var request = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
//set the auth cookie so we don't just get the login page
request.SetAuthenticationCookie();
//get the response
string responseString = request.GetResponseString();
//return it as an html string
return new MvcHtmlString(responseString);
}
private static UrlHelper GetUrlHelper()
{
return new UrlHelper(HttpContext.Current.Request.RequestContext);
}
/// <summary>
/// Gets an absolute version of the specified url relative to the current requests url root.
/// </summary>
/// <param name="thisHelper">The Url helper instance that this method extends.</param>
/// <param name="url">The url to get an absolute version of.</param>
/// <returns>An absolute version of the specified url relative to the current requests url root.</returns>
public static string Absolute(this UrlHelper thisHelper, string url)
{
//only make the url absolute if it isn't already
if (!url.Contains("://"))
{
return string.Format("http://{0}{1}", thisHelper.RequestContext.HttpContext.Request.Url.Authority, thisHelper.Content(url));
}
else
{
return url;
}
}
/// <summary>
/// Sets the authentication cookie of the specified request.
/// </summary>
/// <param name="request">The request to set the cookie for.</param>
/// <param name="authenticationCookie">(Optional) The cookie to add to the request, if not specified defaults
/// to the cookie of the user currently logged into the site.</param>
public static void SetAuthenticationCookie(this HttpWebRequest request, Cookie authenticationCookie = null)
{
if (authenticationCookie == null)
{
//add the current authentication cookie to the request
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
authenticationCookie = new System.Net.Cookie
(
FormsAuthentication.FormsCookieName,
cookie.Value,
cookie.Path,
FormsAuthentication.CookieDomain
);
}
request.CookieContainer = new System.Net.CookieContainer();
request.CookieContainer.Add(authenticationCookie);
}
/// <summary>
/// Gets the response string from the specified request.
/// </summary>
/// <param name="request">The request to get the response string from.</param>
/// <returns>The response string from the specified request.</returns>
public static string GetResponseString(this System.Net.WebRequest request)
{
System.Net.WebResponse response = null;
try
{
response = request.GetResponse();
}
catch (System.Net.WebException webException)
{
response = webException.Response;
throw;
}
return GetResponseString(response);
}
/// <summary>
/// Gets the response string from the specified response.
/// </summary>
/// <param name="response">The response to get the response string from.</param>
/// <returns>The response string from the specified response.</returns>
public static string GetResponseString(this System.Net.WebResponse response)
{
using (var responseTextStream = new System.IO.StreamReader(response.GetResponseStream()))
{
return responseTextStream.ReadToEnd();
}
}
}
Related
Im having a little trouble about extracting some data from Expression<Func<,>>, and having more trouble explaining what i want.
So i have a rest API and im building a method that could work as WFC.
meaning that i will not be needing to call the rest api by url but.
Building an interface or some kind of wrapper and from that interface we could extract the rest api controller Name, method and parameters.
Ok let me show you how i have imagine it to work.
This is my controller interface
[Route(url: "api/")]
public interface IYoutubeController
{
/// <summary>
/// Get a collection of the searched youtube videos
/// </summary>
/// <param name="searchString"></param>
/// <param name="pageSize"></param>
/// <param name="relatedTo"></param>
/// <param name="videoSearchType"></param>
/// <returns></returns>
[Route]
YoutubeVideoCollection Search(string searchString, int pageSize = 50, string relatedTo = null, VideoSearchType videoSearchType = VideoSearchType.Videos);
/// <summary>
/// Get the playlist video contents
/// </summary>
/// <param name="playListId"></param>
/// <returns></returns>
[Route]
List<YoutubeItem> Playlist(string playlistId);
/// <summary>
/// decrypted youtube video
/// </summary>
/// <param name="videoId"></param>
/// <returns></returns>
[Route(httpMethod: HttpMethod.POST)]
Task<YoutubeVideoInfo> GetVideoAsync(string videoId);
}
This the controller Repository
public static class ControllerRepository
{
public static async Task<P> Execute<P>(Expression<Func<IYoutubeController, P>> func)
{
return await HttpHelper.ExecuteAsync(func);
}
}
Now i could simple call my method like this
YoutubeVideoCollection test = await ControllerRepository.Execute(x => x.Search("Eminem"));
You can se that there is no method seach exist, its only a method in interface.
And here is a httphelper, in it exist PostAsync and GetAsync and also ExecuteAsync
private static string baseUrl = "http://xxx"
public static async Task<P> ExecuteAsync<T, P>(Expression<Func<IYoutubeController, P>> func)
{
var url= typeof(T).GetCustomAttribute<Route>()?.Url ?? ""; // eg api/
var controller = typeof(T).Name; // eg IYoutubeContrller, will be renamed to youtube later on
var method = // get the method from func P which is Search
var parameters = // the parameter data from func which is Eminem
var fullUrl= $"{baseUrl}/{url}/{controller}"
// and here we do PostAsync<P>(fullUrl, parameters ) Or GetAsync<P>(fullUrl, parameters )
}
In HttpHelper ExecuteAsync i want to retrive from my Func<T, P> the Method name which is Search and the parameter which is searchString and also the value of the parameter which is Eminem
Could you help me retrive these information from func parameter?
This is still an idee, so it may not really work, just let me know if its possible.
For your very special case:
Execute(x => x.Search("Eminem"));
You could do this
public static async Task<P> ExecuteAsync<T, P>(Expression<Func<IYoutubeController, P>> func)
{
MethodCallExpression callExpression = expression.Body as MethodCallExpression;
string methodName = callExpression.Method.Name;
object argument = ((ConstantExpression)callExpression.Arguments).Value;
// do something
}
But of course this will crash if the expression passed to Execute is more complicated or uses calls with no arguments or non-constant expression arguments etc.
But in that case, you wouldn't know which information to extract at all.
I want my application to respect browser accept header and return 406, if it does not match the response format.
I have this options set in Mvc configuration:
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services">The collection of the services.</param>
/// <returns>The provider of the service.</returns>
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// add mvc services
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.CacheProfiles.Add(
"CodebookCacheProfile",
new CacheProfile()
{
Duration = (int)TimeSpan.FromDays(1).TotalSeconds
});
})
.AddControllersAsServices()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new StringEmptyToNullConverter());
options.SerializerSettings.Converters.Add(new StringEnumConverter(true));
});
// add response compression services
services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
});
// add application services
services.AddSwaggerDoc()
.AddConfiguration(configuration)
.AddModel()
.AddDataAccess(configuration)
.AddAuthentication(configuration)
.AddDomainServices()
.AddSchedulerContainer(() => serviceProvider);
// initialize container
serviceProvider = services.CreateServiceProvider();
return serviceProvider;
}
When I try to send request like this: (with Accept header set on whatever, for example "text/xml")
I always get 200 OK - with "application/json"
My CountriesController looks like this:
/// <summary>
/// REST API controller for actions with countries.
/// </summary>
[AllowAnonymous]
[Area(Area.Common)]
[Route("[area]/Codebooks/[controller]")]
[ResponseCache(CacheProfileName = "CodebookCacheProfile")]
public class CountriesController : ApiController
{
private readonly ICountryService countryService;
/// <summary>
/// Initializes a new instance of the <see cref="CountriesController" /> class.
/// </summary>
/// <param name="countryService">The country service.</param>
public CountriesController(ICountryService countryService)
{
this.countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
}
/// <summary>
/// Gets countries by search settings.
/// </summary>
/// <response code="200">The countries was returned correctly.</response>
/// <response code="401">The unauthorized access.</response>
/// <response code="406">The not acceptable format.</response>
/// <response code="500">The unexpected error.</response>
/// <param name="countrySearchSettings">The search settings of the country.</param>
/// <returns>Data page of countries.</returns>
[HttpGet]
[ProducesResponseType(typeof(IDataPage<Country>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status406NotAcceptable)]
[ProducesResponseType(typeof(ApiErrorSummary), StatusCodes.Status500InternalServerError)]
[SwaggerOperation("SearchCountries")]
public IDataPage<Country> Get([FromQuery(Name = "")] CountrySearchSettings countrySearchSettings)
{
return countryService.Search(countrySearchSettings);
}
/// <summary>
/// Gets a country.
/// </summary>
/// <response code="200">The country was returned correctly.</response>
/// <response code="400">The country code is not valid.</response>
/// <response code="401">The unauthorized access.</response>
/// <response code="406">The not acceptable format.</response>
/// <response code="500">The unexpected error.</response>
/// <param name="countryCode">The code of the country.</param>
/// <returns>Action result.</returns>
[HttpGet("{countryCode}")]
[ProducesResponseType(typeof(Country), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiValidationErrorSummary), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status406NotAcceptable)]
[ProducesResponseType(typeof(ApiErrorSummary), StatusCodes.Status500InternalServerError)]
[SwaggerOperation("GetCountry")]
public IActionResult Get(string countryCode)
{
var country = countryService.GetByCode(countryCode);
return Ok(country);
}
}
Do you have any idea why the request Accept header is always ignored and the response is always 200 OK with correct Json data?
What am I missing? I thought that the setting of RespectBrowserAcceptHeader and ReturnHttpNotAcceptable would do the thing... but apparently not.
Why it always falls back to the default Json formatter?
For ReturnHttpNotAcceptable to work, the type returned by an action must be either an ObjectResult (e.g. Ok(retval)) or a type that does not implement IActionResult (in which case the MVC framework will wrap it in an ObjectResult for you).
This is because the MVC framework only checks against the value of ReturnHttpNotAcceptable in ObjectResultExecutor, and not in any of the other IActionResultExecutor implementations (like ViewResultExecutor). (See source code for ObjectResultExecutor and ViewResultExecutor)
Put simply, make sure that the type you are returning doesn't implement (or inherit from anything that implements) IActionResult.
Add the following line in your ConfigureServices method of Startup.cs
services.AddMvcCore().AddJsonFormatters().AddApiExplorer();
Using .NET 4.5.1, Web API 2, Visual Studio 2013:
I have a Web API which has the following routes...
/api/providers/specialties
/api/providers/specialties/123
/api/providers/specialties/medicine
These work as expected... the first one gets a list of all specialties, the second one gets specialty ID 123, and the third gets all specialties with "medicine" in the name.
I also have these routes...
/api/locations/specialties
/api/locations/specialties/123
/api/locations/specialties/ortho
Only the last two work... the first one returns this error:
No HTTP resource was found that matches the request URI [...]
No route providing a controller name was found to match request URI 'http://mysite/api/locations/specialties'
How can this be? It will hit other routes in that controller, just not the base one.
(I also have two other controllers with the routes /api/providers and /api/locations by themselves, which work fine.)
Here is the ProviderSpecialtyController.cs code:
[RoutePrefix("api/providers/specialties")]
public class ProviderSpecialtyController : ApiController
{
private ProviderEntities db = new ProviderEntities();
/// <summary>
/// Get ALL specialties, sorted by name.
/// </summary>
[Route("")]
public IQueryable<ProviderSpecialty> Get()
{
return db.ProviderSpecialties.OrderBy(s => s.Name);
}
/// <summary>
/// Get a specific specialty.
/// </summary>
/// <param name="id">The ID of a particular specialty.</param>
[Route("{id:int}")]
public ProviderSpecialty Get(int id)
{
return db.ProviderSpecialties.Where(s => s.Id == id).FirstOrDefault();
}
/// <summary>
/// Get all specialties that contain a keyword.
/// </summary>
/// <param name="keyword">The keyword to search for in a specialty name.</param>
[Route("{keyword:alpha}")]
public IQueryable<ProviderSpecialty> Get(string keyword)
{
return db.ProviderSpecialties.Where(s => s.Name.Contains(keyword)).OrderBy(s => s.Name);
}
}
And here is the LocationSpecialtyController.cs code:
[RoutePrefix("api/locations/specialties")]
public class LocationSpecialtyController : ApiController
{
private ProviderEntities db = new ProviderEntities();
/// <summary>
/// Get ALL specialties, sorted by name.
/// </summary>
[Route("")]
public IQueryable<LocationSpecialty> Get()
{
return db.LocationSpecialties.OrderBy(s => s.Name);
}
/// <summary>
/// Get a specific specialty.
/// </summary>
/// <param name="id">The ID of a particular specialty.</param>
[Route("{id:int}")]
public LocationSpecialty Get(int id)
{
return db.LocationSpecialties.Where(s => s.Id == id).FirstOrDefault();
}
/// <summary>
/// Get all specialties that contain a keyword.
/// </summary>
/// <param name="keyword">The keyword to search for in a specialty name.</param>
[Route("{keyword:alpha}")]
public IQueryable<LocationSpecialty> Get(string keyword)
{
return db.LocationSpecialties.Where(s => s.Name.Contains(keyword)).OrderBy(s => s.Name);
}
}
As you can see, they are nearly identical except for the route prefix. Why does the provider controller work as expected but location controller does not?
I have enabled tracing and the following is observed when trying to hit /api/locations/specialties:
System.Web.Http.Request: GET http://localhost:49565/api/locations/specialties/: Category=System.Web.Http.Request, Level=Info Begin http://localhost:49565/api/locations/specialties/
System.Web.Http.Controllers: GET http://localhost:49565/api/locations/specialties/: Category=System.Web.Http.Controllers, Level=Info Begin DefaultHttpControllerSelector SelectController Route='MS_SubRoutes:System.Web.Http.Routing.IHttpRouteData[]'
[...]
System.Web.Http.Controllers: GET http://localhost:49565/api/locations/specialties/: Category=System.Web.Http.Controllers, Level=Error End DefaultHttpControllerSelector SelectController Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
This was simpler than it seemed, but determining why was made more difficult by poor debugging (which was filed and verified as a bug in Codeplex by Kiran Challa. This should be fixed as of Web API 2.1.
I had a controller with this route:
/api/locations/keyword
Which would do a keyword search on keyword.
I had another controller with these routes:
/api/locations/specialties
/api/locations/specialties/123
/api/locations/specialties/keyword
The API engine was confused, because I had two controllers with essentially the same route. I removed one and the problem was fixed.
According to the Codeplex issue tracker, the issue was verified, closed and a new error message was added in Web API 2.1.
I've got this extension method that check for some info in the request cookie and if this info I'm looking for is not in the cookie I go to the database.
All this is OK but when I built this extension I designed it in my MVC4 application, and now I am refactoring items to relative library's and I cannot seem to find a way to import or add a reference to my class library that would allow me to throw an HttpResponseException.
Any ideas on how to overcome this?
namespace LinkedIn.Extensions
{
public static class httprequest_linkedincontext
{
/// <summary>
/// Extension that creates a OAuthLinkedIn object by checking the cookie first, and if the cookie has no LinkedIn info or has expried then builds the LinkedIn OAuthLinkedIn from the database.
/// </summary>
/// <param name="request"></param>
/// <param name="userName"></param>
/// <returns>oAuthLinkedIn information</returns>
public static oAuthLinkedIn GetLinkedInContext(this HttpRequestMessage request, string userName)
{
OAuthHelper helper = new OAuthHelper(request, userName, false);
oAuthLinkedIn oauth = new oAuthLinkedIn(helper.Verify, helper.Token, helper.TokenSecret);
if (string.IsNullOrEmpty(oauth.Token) || string.IsNullOrEmpty(oauth.TokenSecret))
{
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
}
return oauth;
}
}
}
Right now there is a little red squiggle line under Http in the System.Web.Http.HttpResponseException.
My question is very similar to this issue: AntiForgery Exception: A required anti-forgery token was not supplied or was invalid
but I have the MVC3 and I using Razor installed.
controller has the
[ValidateAntiForgeryToken]
specified
in html is printed <input name="__RequestVerificationToken"... using #Html.AntiForgeryToken()
Also I observed, that if I remove the Authorization cookie in the browser, and controller method does not have [Authorize] I don't have any problems with AntiForery. Why?
Check your cookies and make sure that you are seeing the requestVerificationToken cookie being set correctly. I have run into this before where the cookies for the site were all set to be SSL only and I was trying to run it over regular HTTP locally, so the cookie was never being accepted because it was being transmitted over unsecure channels.
For me, this meant changing a line in the web.config under system.web/httpCookies to requireSSL="false"... but if this isn't what you are seeing, I would still look at things that might be messing with your cookies in the system (e.g. session resets, manually clearing the cookies somewhere, etc.). If you have the validation attribute on the controller methods correctly, and are still getting this, it is likely due to something modifying or removing that cookie!
Edit: Also, if you have this on the controller instead of only on the POST methods, that would be why... This is only applicable to form POSTs to the server.
Here's a simple custom version that you CAN apply to the form that will automatically validate on ALL POST action methods:
/// <summary>
/// Custom Implementation of the Validate Anti Forgery Token Attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CustomValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
/// <summary>
/// The ValidateAntiForgeryTokenAttribute.
/// </summary>
private readonly ValidateAntiForgeryTokenAttribute _validator;
/// <summary>
/// The AcceptVerbsAttribute.
/// </summary>
private readonly AcceptVerbsAttribute _verbs;
/// <summary>
/// Initializes a new instance of the <see cref="CustomValidateAntiForgeryTokenAttribute"/> class.
/// </summary>
/// <param name="verbs">The verbs.</param>
public CustomValidateAntiForgeryTokenAttribute(HttpVerbs verbs) : this(verbs, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomValidateAntiForgeryTokenAttribute"/> class.
/// </summary>
/// <param name="verbs">The verbs.</param>
/// <param name="salt">The salt.</param>
public CustomValidateAntiForgeryTokenAttribute(HttpVerbs verbs, string salt)
{
_verbs = new AcceptVerbsAttribute(verbs);
_validator = new ValidateAntiForgeryTokenAttribute
{
Salt = salt
};
}
/// <summary>
/// Called when authorization is required.
/// </summary>
/// <param name="filterContext">The filter context.</param>
public void OnAuthorization(AuthorizationContext filterContext)
{
var httpMethodOverride = filterContext.HttpContext.Request.GetHttpMethodOverride();
var found = false;
foreach (var verb in _verbs.Verbs)
{
if (verb.Equals(httpMethodOverride, StringComparison.OrdinalIgnoreCase))
{
found = true;
}
}
if (found && !filterContext.RequestContext.RouteData.Values["action"].ToString().StartsWith("Json"))
{
_validator.OnAuthorization(filterContext);
}
}
}
Then you can just add the following to all of your controllers, or to your base controller if you override and inherit from one:
[CustomValidateAntiForgeryToken(HttpVerbs.Post)]
Anti forgery token is tied to the user identity. If you changing currently logged in user identity between generating and validating tokens then token will not be validated successfully. Also, that explains why everything is working for you in anonymous mode.