I am trying to localize the DisplayFormat of several of my view models. I have been able to localize the Display:Name, Required and RegularExpression messages all from a shared resource file in a separate project.
In addition, I have been able to localize my razor views and any messages generated from my controllers. After some research, it appears I can't localize the DisplayFormat in the same manner as the other data annotations. Other posts on SO indicate I should create a custom attribute that inherits from Attribute or DisplayAtttribute.
DisplayFormat data annotation using resource string
Model Class DisplayFormat() How can I localization NullDisplayText?
Ideally I would like to retrieve the correct format string from my shared resource file within the custom attribute while passing in the ResourceKey name. I am not sure how to go about setting this up. Possibly using the IStringLocalizer<SharedResource>?
I have an extension method to setup localization services at startup
public static class LocalizationExtensions
{
public static IServiceCollection AddLocalizationServices(this IServiceCollection services)
{
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("es-MX")
};
options.DefaultRequestCulture = new RequestCulture("en-US");
// Formatting numbers, dates, etc.
options.SupportedCultures = supportedCultures;
// UI strings that we have localized.
options.SupportedUICultures = supportedCultures;
});
services.AddLocalization(options => { options.ResourcesPath = "Resources"; } );
services.AddControllersWithViews()
.AddRazorRuntimeCompilation()
.AddViewLocalization(options => {
options.ResourcesPath = "Resources";
})
.AddDataAnnotationsLocalization(options => {
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(SharedResource));
};
});
//services.AddScoped<RequestLocalizationCookiesMiddleware>();
return services;
}
}
In my configure method in startup
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
Any help would be appreciated.
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 want to use localization in an ASP.NET Core applciation that uses Areas.
I have got a partial view Areas\Admin\Views\People\GetPeopleStatistics.cshtml
In this I want to use localiuation:
...
#inject IViewLocalizer Localizer
<h3>#Localizer["People Statistics"]:</h3>
...
I created a resource file for this: Resources\Admin\Views\People\GetPeopleStatistics.en.resx
I have the following configuration in Startup.cs:
services.AddLocalization(opts => { opts.ResourcesPath = "Resources"; });
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();
services.AddAutoMapper();
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("de")
};
opts.DefaultRequestCulture = new RequestCulture("en");
// Formatting numbers, dates, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized.
opts.SupportedUICultures = supportedCultures;
});
Unfortunatelly this does not work. The application does not display the value from the resource file.
I am using Cookies to store the culture:
[HttpPost]
public async Task SetLanguage(string culture)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
}
I also get the following error in Visual Studio:
Custom tool PublicResXFileCodeGenerator failed to produce an output for input file 'Resources\Admin\Views\People\GetPeopleStatistics.en.resx' but did not log a specific error. WebApplication D:\SVN Repositories\SRMS\trunk\PresentationLayer\WebApplication\Resources\Admin\Views\People\GetPeopleStatistics.en.resx 1
What am I doing wrong? How can I configure this?
You might want try the following: rightclick your solution and do 'clean solution'. That solved the issue for me when localization was not working and I was 100% sure everything was configured correctly.
I am new to unit testing .net applications and am having difficulty with what I would imagine to be a very simple case.
// GET: Entities
public ViewResult Index()
{
_User = UserManager.FindById(User.Identity.GetUserId());
return View(entityRepository.GetEntities(_User.entityId));
}
I want to test that the correct view is outputted but can't get past the user line. In other languages I would simply mock UserManager.FindById to always return some predefined object, but I can't get it to work.
Have been trying to follow the approach given here example mocking the IUserStore but can't get it to work with my example. To mock FindByNameAsync they have used store.As
Any advice would be gratefully received.
My attempt was to follow a similar method to that in link above. Obviously IUserPasswordStore is the wrong interface, but I am not sure how to find the correct one.
var store = new Mock<IUserStore<ApplicationUser>>(MockBehavior.Strict);
store.As<IUserPasswordStore<ApplicationUser>>()
.Setup(x => x.FindById(It.IsAny<string>()))
.Returns(ApplicationUser)null);
EntitiesController controller = new EntitiesController();
var result = controller.Index() as ViewResult;
Assert.AreEqual("Index", result.ViewName);
So, thanks to the guidance from #Nkosi and #haim770 I think I have an answer. It still seems overly complex though, so would be interested to hear if you know how to simplify.
It started with needing to write more testable code, I installed Unity and started to inject dependencies allowing me to mock them out.
The solution that passes is:
Mock<IPrincipal> mockPrincipal;
string username = "test#test.com";
[TestInitialize]
public void TestInitialize()
{
//Arrange
var identity = new GenericIdentity(username, "");
var nameIdentifierClaim = new Claim(ClaimTypes.NameIdentifier, username);
identity.AddClaim(nameIdentifierClaim);
mockPrincipal = new Mock<IPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
}
[TestMethod]
public void EntitiesIndexDisplaysTheDefaultView()
{
var context = new Mock<HttpContextBase>();
var principal = mockPrincipal.Object;
context.Setup(x => x.User).Returns(principal);
var userManagerMock = new Mock<IUserStore<ApplicationUser>>(MockBehavior.Strict);
userManagerMock.As<IUserPasswordStore<ApplicationUser>>()
.Setup(x => x.FindByIdAsync(It.IsAny<string>()))
.ReturnsAsync(new ApplicationUser() { entityId = "id" });
var entitiesRepositoryMock = new Mock<IEntityRepository>();
entitiesRepositoryMock
.Setup(x => x.GetEntities(It.IsAny<string>()))
.Returns((IEnumerable<Entity>)new List<Entity>());
EntitiesController controller = new EntitiesController(new UserManager<ApplicationUser>(userManagerMock.Object),
entitiesRepositoryMock.Object);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
var result = controller.Index() as ViewResult;
Assert.AreEqual("", result.ViewName);
}
I'm running a single instance of Orchard CMS on my web server with two custom modules, ModuleFirst and ModuleSecond. For reasons, I want these two to act as separate websites with their own domain and homepage. I can not set up additional websites or use Orchard's built-in Tenants feature.
What I have
The way I went about achieving this is as follows:
Added two bindings to my website in IIS: first-domain.com and second-domain.com
Implemented a ThemeSelector (which I think acts like an ActionFilter) that switches the theme based on the host of the Url in the incoming Request
if (host.Contains("second-domain.com"))
{
useSecondTheme = true;
}
Make sure all routes are unique
This is working reasonably well for the most part. I can navigate to first-domain.com/foo and second-domain.com/bar and it looks like I'm on different websites.
The problem
For the two "homepages" I can't make a unique route because I don't want to add any suffixes. Both projects define a blank route that should lead to their respective Home/Index but I can't figure out how to make this work.
new RouteDescriptor {
Priority = 90,
Route = new Route(
"",
new RouteValueDictionary { // Defaults
{"area", "ModuleFirst"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary(), // Constraints
new RouteValueDictionary { // Datatokens
{"area", "ModuleFirst"}
},
new MvcRouteHandler())
}
new RouteDescriptor {
Priority = 100,
Route = new Route(
"",
new RouteValueDictionary { // Defaults
{"area", "ModuleSecond"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary(), // Constraints
new RouteValueDictionary { // Datatokens
{"area", "ModuleSecond"}
},
new MvcRouteHandler())
}
What I tried
I tried to implement an ActionFilter that Redirects to ModuleFirst/Home/Index when a request with host url first-domain.com reaches ModuleSecond/Home/Index but this obviously doesn't work since it just keeps hitting the highest priority route over and over and breaks the website.
I have also tried to implement a custom RouteConstraint on the route with the highest priority to block all incoming request that don't come from its intended domain, assuming that those would then fall back on the lower priority route.
public class SecondConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.Url.DnsSafeHost.Contains("second-domain.com");
}
}
used as follows:
new RouteDescriptor {
Priority = 100,
Route = new Route(
"",
new RouteValueDictionary { // Defaults
{"area", "ModuleSecond"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary { // Constraints
{"isSecond", new SecondConstraint()}
},
new RouteValueDictionary { // Datatokens
{"area", "ModuleSecond"}
},
new MvcRouteHandler())
}
I can now navigate to second-domain.com just fine and get the correct page but navigating to first-domain.com times out. I haven't managed to find any examples of RouteConstraints in Orchard though, so maybe I'm doing it wrong.
Though I would recommend anyone who is about to do something like this from scratch to use tenants and OAuth instead if possible, as Bertrand suggests in his comment, I thought of a fairly clean way to accomplish what I wanted.
Instead of using two separate modules, I just use one.
In my one module, I implement IRouteProvider to override the default route, which catches both first-domain.com/ and second-domain.com/
public class Routes : IRouteProvider
{
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[] {
new RouteDescriptor {
Priority = 100,
Route = new Route(
"",
new RouteValueDictionary {
{"area", "MyModule"},
{"controller", "Home"},
{"action", "Index"},
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "MyModule"}
},
new MvcRouteHandler())
}
};
}
}
Then I created a custom Part with just a single property bool IsHomepage and attached it to the Page Type
public class SecondHomepagePart : ContentPart<SecondHomepagePartRecord>
{
public bool IsHomepage
{
get { return Retrieve(r => r.IsHomepage); }
set { Store(r => r.IsHomepage, value); }
}
}
In the driver I make sure that there is only ever one "second homepage"
protected override DriverResult Editor(SecondHomepagePart part, IUpdateModel updater, dynamic shapeHelper)
{
if (updater.TryUpdateModel(part, Prefix, null, null))
{
if (part.IsHomepage)
{
var otherHomepages = _orchardServices.ContentManager.Query<SecondHomepagePart>().Where<SecondHomepagePartRecord>(r => r.IsHomepage && r.Id != part.Id).List();
foreach (var homepagePart in otherHomepages)
{
homepagePart.IsHomepage = false;
}
}
}
return Editor(part, shapeHelper);
}
Then in my custom HomeController I just check the current domain, fetch the ContentItem with the blank AutoroutePart or the SecondHomepagePart where IsHomepage is set to true depending on the domain, build the Detail Shape of that item and then display that Shape in my custom Index.cshtml view.
Controller:
public ActionResult Index()
{
var host = HttpContext.Request.Url.DnsSafeHost;
var model = new IndexViewModel();
IContent homepageItem;
if (host.Contains("first-domain.com"))
{
homepageItem = _homeAliasService.GetHomePage();
}
else
{
homepageItem = _orchardServices.ContentManager.Query<SecondHomepagePart>()
.Where<SecondHomepagePartRecord>(r => r.IsHomepage)
.List()
.FirstOrDefault();
}
model.Homepage = _orchardServices.ContentManager.BuildDisplay(homepageItem, "Detail");
return View(model);
}
View:
#model MyModule.ViewModels.Home.IndexViewModel
#if (Model.Homepage != null)
{
#Display(Model.Homepage)
}
I use the same check in an IConditionProvider and IThemeSelector to have a layer rule for each domain and to set the appropriate theme automatically. Now I essentially have two completely different websites to the outside world but with shared content, widgets, parts, custom settings etc. This is a quick and easy solution for a client who sells the same products from the same stock but under different brandings and under different conditions, along with some exclusive content that's different for both.