i'm new to asp.net core trying to implement localization to support multiple language , my configuration is like this
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr-FR")
};
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
services.AddMvc().AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,opts=> {opts.ResourcesPath="Resources"; })
.AddDataAnnotationsLocalization(o=> {
o.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(SharedResource));
};
});
this is the standard configuration and its working fine , i create a method inside a controller
[Route("api/setlanguage")]
[HttpPost]
public IActionResult SetLanguage (string culture)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
throw new Exception(_localizer["Hello",culture]);
}
when i test with Postman like this : http://localhost:31563/api/SetLanguage?culture=en-US i'm getting a correct result but when i try to pass culture inside the body of request its not working , can anyone help me on this , thanks so much
to make this work i just need to add
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
to force my currentThread.CurrentCulture/UICulture to take the culture value that i'm passing from body
public IActionResult SetLanguage([FromBody] string culture)
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
_localizer.WithCulture(new CultureInfo(culture));
}
By default, Web API parameter will get value from query string if you use like (string culture).
If you want to get value from Body, you could add [FromBody] like below:
public IActionResult SetLanguage([FromBody]string culture)
And then post your request like below:
Obviously, it will not work with Query String request if you add [FromBody].
Related
We have an ASP.NET Core 2 MVC application and an action method Search which normally returns an IActionResult in the following form:
return View("SearchResult", Model);
How can we manipulate the return url?
What we would like to do is to take the QueryString and Add/Remove various keys using the QueryHelpers and other built-in functionalities and then change the return Url to that with the new QueryString.
If we just leave return View("SearchResult", Model); the original Url is used and not the changed one.
Any assistance would be greatly appreciated.
You could not control the web browser url directly from server side.
For a workaround, you could try to URL Rewriting Middleware in ASP.NET Core like
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSession();
var options = new RewriteOptions()
.Add(context => {
var request = context.HttpContext.Request;
var query = request.Query;
StringValues color = "";
if (request.Path.StartsWithSegments("/Home/Test", StringComparison.OrdinalIgnoreCase)
&& query.TryGetValue("color", out color))
{
var response = context.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
context.HttpContext.Session.SetString("query", request.QueryString.Value);
var items = query.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToList();
// At this point you can remove items if you want
items.RemoveAll(x => x.Key == "color"); // Remove all values for key
var qb = new QueryBuilder(items);
response.Headers[HeaderNames.Location] =
request.Path + qb;
}
else if (request.Path.StartsWithSegments("/Home/Test", StringComparison.OrdinalIgnoreCase)
&& !query.TryGetValue("color", out color))
{
context.Result = RuleResult.SkipRemainingRules;
request.QueryString = new QueryString(context.HttpContext.Session.GetString("query"));
}
});
app.UseRewriter(options);
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
I have three languages on my website. I'm trying to get my razor pages to route to culture/localization like so:
https://localhost:44396/en/
https://localhost:44396/ru/
I have hundreds of lines of code commented out at this point using methods I've been googling for the past two days and nothing seems to do the job.
The website is mostly static so right now beyond the culture there is nothing else that needs routing.
Here's a way you can do it that doesn't require you to put a middleware attribute on all of your pages. This works globally.
In the ConfigureServices method of Startup.cs, add the following:
services.AddMvc().AddRazorPagesOptions(options => {
options.Conventions.AddFolderRouteModelConvention("/", model => {
foreach (var selector in model.Selectors) {
selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{lang=en}", selector.AttributeRouteModel.Template);
}
});
});
services.Configure<RequestLocalizationOptions>(options => {
var defaultCulture = new CultureInfo("en");
var supportedCultures = new CultureInfo[] {
defaultCulture,
new CultureInfo("fr")
};
options.DefaultRequestCulture = new RequestCulture(defaultCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider() {
RouteDataStringKey = "lang",
UIRouteDataStringKey = "lang",
Options = options
});
});
This sets up the global route, your supported cultures, and sets the primary culture provider to come from the route. (This still leaves the other providers intact, so failing the Route values, it can still set the culture based on the Query String, Cookies, or Language Header.)
Now, in your Configure method (still in Startup.cs), add the following:
var routeBuilder = new RouteBuilder(app) {
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
routeBuilder.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
var router = routeBuilder.Build();
app.Use(async (context, next) => {
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
context.Features[typeof(IRoutingFeature)] = new RoutingFeature() {
RouteData = routeContext.RouteData
};
await next();
});
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
app.UseMvc();
There's some trickery here. Firstly, we have to call app.UseRequestLocalization before we call app.UseMvc, or else our program will run before we've changed the current culture. But the problem is, app.UseMvc() is the one that sets up RouteData. So, until you call it, the routing values are all blank. Ergo, when the RouteDataRequestCultureProvider goes to try and observe what {lang} is, it'll come back empty, and thus always default you to en. Catch 22.
So, we just go manually populate the RouteData ourselves in our own custom middleware. That way, the RouteDataRequestCultureProvider can see it, and all will work well.
(I admit this is not the most efficient, as you're just duplicating the routing work that app.UseMvc() will itself also do, but I'll take that unnoticeable delay to ensure all my pages are localized.)
I will tell you what I do which works. The only difference is that I use the 5 characters language code but I guess it is not something difficult to change.
Make sure that you have the following nuget library installed
Microsoft.AspNetCore.Localization.Routing
In the ConfigureServices method of the Startup.cs we type the following code under the servcies.AddMvc();
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Account/Manage");
options.Conventions.AuthorizePage("/Account/Logout");
options.Conventions.AddFolderRouteModelConvention("/", model =>
{
foreach (var selector in model.Selectors)
{
var attributeRouteModel = selector.AttributeRouteModel;
attributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{lang=el-GR}", attributeRouteModel.Template);
}
});
});
IList<CultureInfo> supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
new CultureInfo("el-GR"),
};
var MyOptions = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
MyOptions.RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider() { RouteDataStringKey = "lang", Options = MyOptions } // requires nuget package Microsoft.AspNetCore.Localization.Routing
};
services.AddSingleton(MyOptions);
We add the following class
using Microsoft.AspNetCore.Builder;
public class LocalizationPipeline
{
public void Configure(IApplicationBuilder app, RequestLocalizationOptions options)
{
app.UseRequestLocalization(options);
}
}
Now you have to add the following line over your PageModel class:
[MiddlewareFilter(typeof(LocalizationPipeline))]
public class ContactModel : PageModel
{
public void OnGet()
{
}
}
I hope it helps.
I have this function and unit test.
ProfileController Code
[HttpPost]
public ActionResult Edit(int? id)
{
var redirectUrl = new UrlHelper(Request.RequestContext).Action("Index", "Profile", new { id = 0 });
return Json(new { Url = redirectUrl });
}
unit test code
[TestMethod]
public void TestDetailsViewData()
{
var controller = new ProfileController(_Profile);
var result = controller.Edit(1) as ViewResult;
var profile = (VIEWMODELS.Customer.Profile)result.ViewData.Model;
Assert.AreEqual("Testor", profile.cardName);
}
i would like to test this function and this function will redirect to index page and return ViewPage with data. But the problem is when i run this unit test code i got Null exception at this line ,because of the Request is NULL
var redirectUrl = new UrlHelper(Request.RequestContext).Action("Index", "Profile", new { id = 0 });
so may i know how could i test this?
Updated with Moq function
After further reading and get some sample code from here it seem request able to filled with value but now the RequestContext is NULL, anyone can point me to right place for me to study?
[TestMethod]
public void TestDetailsViewData()
{
var request = new Mock<HttpRequestBase>();
request.SetupGet(x => x.Headers).Returns(
new System.Net.WebHeaderCollection {
{"X-Requested-With", "XMLHttpRequest"}
});
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
//context.SetupGet(x => x.Request.RequestContext ).Returns(request.Object.RequestContext);
var controller = new ProfileController(_Profile);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
controller.Request.RequestContext = context.Object.Request.RequestContext;
var result = controller.Edit(1) as ViewResult;
var profile = (VIEWMODELS.Customer.Profile)result.ViewData.Model;
Assert.AreEqual("testor", profile.cardName);
}
The error is occurring because the request is not defined in Test method.I think you have to use visual studio fakes.
https://msdn.microsoft.com/en-us/library/hh549175.aspx
I am trying to POST an object from a WebJob to an MVC 4 controller. I am using Entity Framework. In the controller, I cannot get the object to bind properly (the argument is null). I have looked at many tutorials and it seems like my code should work.
Model (does this need to be in a specific namespace for EF to find it?):
public class CreateListingObject
{
public Listing listing;
public List<GalleryImage> images;
public CreateListingObject()
{
listing = new Listing();
images = new List<GalleryImage>();
}
}
public struct GalleryImage
{
public string picURL;
public string caption;
}
POST:
public void PostListing(CreateListingObject o)
{
Console.WriteLine("Posting listing: {0}", o.listing.Title);
HttpClient _httpClient = new HttpClient();
Uri uri = new Uri(_serviceUri, "/Automaton/CreateTestListing");
string json = BizbotHelper.SerializeJson(o);
HttpResponseMessage response = BizbotHelper.SendRequest(_httpClient, HttpMethod.Post, uri, json);
string r = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
}
SendRequest (thank you Azure search samples):
public static HttpResponseMessage SendRequest(HttpClient client, HttpMethod method, Uri uri, string json = null)
{
UriBuilder builder = new UriBuilder(uri);
//string separator = string.IsNullOrWhiteSpace(builder.Query) ? string.Empty : "&";
//builder.Query = builder.Query.TrimStart('?') + separator + ApiVersionString;
var request = new HttpRequestMessage(method, builder.Uri);
if (json != null)
{
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
return client.SendAsync(request).Result;
}
Controller Action fragment (o is an empty object here):
[HttpPost]
public ActionResult CreateTestListing(CreateListingObject o)
{
Listing li = o.listing;
I have confirmed that if I post a simple object using the same code, everything works as expected.
Instead of sending a CreateListingObject in PostListing, I send this instead:
var test = new
{
data = "hi mom"
};
And change my action to, then the argument gets bound and I get valid data:
[HttpPost]
public ActionResult CreateTestListing(string data)
{
I have also checked the serialization of my CreateListingObject in the WebJob, and it is fully populated as I expect. This leads me to suspect that I am falling afoul of the default ModelBinder.
I have a controller in C# using the ASP.Net MVC framework
public class HomeController:Controller{
public ActionResult Index()
{
if (Request.IsAjaxRequest())
{
//do some ajaxy stuff
}
return View("Index");
}
}
I got some tips on mocking and was hoping to test the code with the following and RhinoMocks
var mocks = new MockRepository();
var mockedhttpContext = mocks.DynamicMock<HttpContextBase>();
var mockedHttpRequest = mocks.DynamicMock<HttpRequestBase>();
SetupResult.For(mockedhttpContext.Request).Return(mockedHttpRequest);
var controller = new HomeController();
controller.ControllerContext = new ControllerContext(mockedhttpContext, new RouteData(), controller);
var result = controller.Index() as ViewResult;
Assert.AreEqual("About", result.ViewName);
However I keep getting this error:
Exception
System.ArgumentNullException:
System.ArgumentNullException : Value
cannot be null. Parameter name:
request at
System.Web.Mvc.AjaxRequestExtensions.IsAjaxRequest(HttpRequestBase
request)
Since the Request object on the controller has no setter. I tried to get this test working properly by using recommended code from an answer below.
This used Moq instead of RhinoMocks, and in using Moq I use the following for the same test:
var request = new Mock<HttpRequestBase>();
// Not working - IsAjaxRequest() is static extension method and cannot be mocked
// request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
// use this
request.SetupGet(x => x.Headers["X-Requested-With"]).Returns("XMLHttpRequest");
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
var controller = new HomeController(Repository, LoginInfoProvider);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
var result = controller.Index() as ViewResult;
Assert.AreEqual("About", result.ViewName);
but get the following error:
Exception System.ArgumentException:
System.ArgumentException : Invalid
setup on a non-overridable member: x
=> x.Headers["X-Requested-With"] at Moq.Mock.ThrowIfCantOverride(Expression
setup, MethodInfo methodInfo)
Again, it seems like I cannot set the request header.
How do I set this value, in RhinoMocks or Moq?
Using Moq:
var request = new Mock<HttpRequestBase>();
// Not working - IsAjaxRequest() is static extension method and cannot be mocked
// request.Setup(x => x.IsAjaxRequest()).Returns(true /* or false */);
// use this
request.SetupGet(x => x.Headers).Returns(
new System.Net.WebHeaderCollection {
{"X-Requested-With", "XMLHttpRequest"}
});
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
var controller = new YourController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
UPDATED:
Mock Request.Headers["X-Requested-With"] or Request["X-Requested-With"] instead of Request.IsAjaxRequest().
For anyone using NSubstitute I was able to modify the above answers and do something like this... (where Details is the Action method name on the controller)
var fakeRequest = Substitute.For<HttpRequestBase>();
var fakeContext = Substitute.For<HttpContextBase>();
fakeRequest.Headers.Returns(new WebHeaderCollection { {"X-Requested-With", "XMLHttpRequest"}});
fakeContext.Request.Returns(fakeRequest);
controller.ControllerContext = new ControllerContext(fakeContext, new RouteData(), controller);
var model = new EntityTypeMaintenanceModel();
var result = controller.Details(model) as PartialViewResult;
Assert.IsNotNull(result);
Assert.AreEqual("EntityType", result.ViewName);
Here is a working solution using RhinoMocks. I've based it on a Moq solution I found at http://thegrayzone.co.uk/blog/2010/03/mocking-request-isajaxrequest/
public static void MakeAjaxRequest(this Controller controller)
{
MockRepository mocks = new MockRepository();
// Create mocks
var mockedhttpContext = mocks.DynamicMock<HttpContextBase>();
var mockedHttpRequest = mocks.DynamicMock<HttpRequestBase>();
// Set headers to pretend it's an Ajax request
SetupResult.For(mockedHttpRequest.Headers)
.Return(new WebHeaderCollection() {
{"X-Requested-With", "XMLHttpRequest"}
});
// Tell the mocked context to return the mocked request
SetupResult.For(mockedhttpContext.Request).Return(mockedHttpRequest);
mocks.ReplayAll();
// Set controllerContext
controller.ControllerContext = new ControllerContext(mockedhttpContext, new RouteData(), controller);
}
Is AjaxRequest is an extension method. So you can do it the following way using Rhino:
protected HttpContextBase BuildHttpContextStub(bool isAjaxRequest)
{
var httpRequestBase = MockRepository.GenerateStub<HttpRequestBase>();
if (isAjaxRequest)
{
httpRequestBase.Stub(r => r["X-Requested-With"]).Return("XMLHttpRequest");
}
var httpContextBase = MockRepository.GenerateStub<HttpContextBase>();
httpContextBase.Stub(c => c.Request).Return(httpRequestBase);
return httpContextBase;
}
// Build controller
....
controller.ControllerContext = new ControllerContext(BuildHttpContextStub(true), new RouteData(), controller);
Looks like you are looking for this,
var requestMock = new Mock<HttpRequestBase>();
requestMock.SetupGet(rq => rq["Age"]).Returns("2001");
Usage in Controller :
public ActionResult Index()
{
var age = Request["Age"]; //This will return 2001
}
In current .NET (v 5):
var controller = new SomeController(); // SomeController that inherits Microsoft.AspNetCore.Mvc.ControllerBase
var httpContext = new DefaultHttpContext(); // DefaultHttpContext class is part of Microsoft.AspNetCore.Http namespace
httpContext.Request.Headers.Add("origin", "0.0.0.1"); // Add your custom headers to request
controller.ControllerContext.HttpContext = httpContext;
You need to mock HttpContextBase and put it into your ControllerContext property, like that:
controller.ControllerContext =
new ControllerContext(mockedHttpContext, new RouteData(), controller);
To make IsAjaxRequest() to return false during Unit test you need to setup Request Headers as well as request collection value both in your test method as given below:
_request.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection { { "X-Requested-With", "NotAjaxRequest" } });
_request.SetupGet(x=>x["X-Requested-With"]).Returns("NotAjaxRequest");
The reason for setting up both is hidden in implementation of IsAjaxRequest() which is given below:
public static bool IsAjaxRequest(this HttpRequestBase request)<br/>
{
if (request == null)
{
throw new ArgumentNullException("request");
}
return ((request["X-Requested-With"] == "XMLHttpRequest") || ((request.Headers != null) && (request.Headers["X-Requested-With"] == "XMLHttpRequest")));
}
It uses both request Collection and header this is why we need to create setup for both Header and Request Collection.
this will make the request to return false when it is not a ajax request. to make it return true you can do the following:
_httpContext.SetupGet(x => x.Request["X-Requested-With"]).Returns("XMLHttpRequest");
I found other way to add a HttpRequestMessage object into your request during Web API as follow
[Test]
public void TestMethod()
{
var controllerContext = new HttpControllerContext();
var request = new HttpRequestMessage();
request.Headers.Add("TestHeader", "TestHeader");
controllerContext.Request = request;
_controller.ControllerContext = controllerContext;
var result = _controller.YourAPIMethod();
//Your assertion
}
(A bit late to the party but I went for a different route so thought I'd share)
To go for a pure code/mocking way of testing this without creating mocks for Http classes I implemented an IControllerHelper which has an Initialise method which takes the Request as a parameter and then exposed properties I want eg:
public interface IControllerHelper
{
void Initialise(HttpRequest request);
string HostAddress { get; }
}
public class ControllerHelper : IControllerHelper
{
private HttpRequest _request;
public void Initialise(HttpRequest request)
{
_request = request;
}
public string HostAddress => _request.GetUri().GetLeftPart(UriPartial.Authority);
}
Then in my controller I call initialise at the start of the method:
_controllerHelper.Initialise(Request);
And then my code is only dependent on mockable dependacies.
return Created(new Uri($"{_controllerHelper.HostName}/api/MyEndpoint/{result.id}"), result);
For functional tests I just override the iControllerHelper in the composition for a substitute.