Getting started with swashbuckle for blank web.api in vs2015 - asp.net-web-api

This is a beginner question. In 2015 I created a blank web.api project. I then added Swashbuckle 5.5.3 nuget package. I created a common controller listed below.
namespace StarterApi.Controllers
{
public class CommonController : ApiController
{
[HttpGet]
public HttpResponseMessage GetTestList(int id)
{
HttpResponseMessage msg = null;
try
{
//var principal = User as ClaimsPrincipal;
List<DDLDispValueVM> ctrList = new List<DDLDispValueVM> {
new DDLDispValueVM { Disp="one", Value="1"},
new DDLDispValueVM { Disp="two", Value="2"}
};
msg = Request.CreateResponse(HttpStatusCode.OK, ctrList);
}
catch (Exception ex)
{
msg = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
return msg;
}
}
}
When I go to the url I get the swagger screen, but it is missing the Controller.
See Image.
I have a GitHub of the project at...
TestSwashbuckle
Here is the SwaggerConfig file
using System.Web.Http;
using WebActivatorEx;
using StarterApi;
using Swashbuckle.Application;
[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
namespace StarterApi
{
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration.EnableSwagger(c =>
{
// By default, the service root url is inferred from the request used to access the docs.
// However, there may be situations (e.g. proxy and load-balanced environments) where this does not
// resolve correctly. You can workaround this by providing your own code to determine the root URL.
//
//c.RootUrl(req => GetRootUrlFromAppConfig());
// If schemes are not explicitly provided in a Swagger 2.0 document, then the scheme used to access
// the docs is taken as the default. If your API supports multiple schemes and you want to be explicit
// about them, you can use the "Schemes" option as shown below.
//
//c.Schemes(new[] { "http", "https" });
// Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
// hold additional metadata for an API. Version and title are required but you can also provide
// additional fields by chaining methods off SingleApiVersion.
//
c.SingleApiVersion("v1", "StarterApi");
// If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
// In this case, you must provide a lambda that tells Swashbuckle which actions should be
// included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
// returns an "Info" builder so you can provide additional metadata per API version.
//
//c.MultipleApiVersions(
// (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
// (vc) =>
// {
// vc.Version("v2", "Swashbuckle Dummy API V2");
// vc.Version("v1", "Swashbuckle Dummy API V1");
// });
// You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API.
// See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details.
// NOTE: These only define the schemes and need to be coupled with a corresponding "security" property
// at the document or operation level to indicate which schemes are required for an operation. To do this,
// you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties
// according to your specific authorization implementation
//
//c.BasicAuth("basic")
// .Description("Basic HTTP Authentication");
//
// NOTE: You must also configure 'EnableApiKeySupport' below in the SwaggerUI section
//c.ApiKey("apiKey")
// .Description("API Key Authentication")
// .Name("apiKey")
// .In("header");
//
//c.OAuth2("oauth2")
// .Description("OAuth2 Implicit Grant")
// .Flow("implicit")
// .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog")
// //.TokenUrl("https://tempuri.org/token")
// .Scopes(scopes =>
// {
// scopes.Add("read", "Read access to protected resources");
// scopes.Add("write", "Write access to protected resources");
// });
// Set this flag to omit descriptions for any actions decorated with the Obsolete attribute
//c.IgnoreObsoleteActions();
// Each operation be assigned one or more tags which are then used by consumers for various reasons.
// For example, the swagger-ui groups operations according to the first tag of each operation.
// By default, this will be controller name but you can use the "GroupActionsBy" option to
// override with any value.
//
//c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString());
// You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate
// the order in which operations are listed. For example, if the default grouping is in place
// (controller name) and you specify a descending alphabetic sort order, then actions from a
// ProductsController will be listed before those from a CustomersController. This is typically
// used to customize the order of groupings in the swagger-ui.
//
//c.OrderActionGroupsBy(new DescendingAlphabeticComparer());
// If you annotate Controllers and API Types with
// Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate
// those comments into the generated docs and UI. You can enable this by providing the path to one or
// more Xml comment files.
//
//c.IncludeXmlComments(GetXmlCommentsPath());
// Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types
// exposed in your API. However, there may be occasions when more control of the output is needed.
// This is supported through the "MapType" and "SchemaFilter" options:
//
// Use the "MapType" option to override the Schema generation for a specific type.
// It should be noted that the resulting Schema will be placed "inline" for any applicable Operations.
// While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not.
// It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only
// use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a
// complex Schema, use a Schema filter.
//
//c.MapType<ProductType>(() => new Schema { type = "integer", format = "int32" });
// If you want to post-modify "complex" Schemas once they've been generated, across the board or for a
// specific type, you can wire up one or more Schema filters.
//
//c.SchemaFilter<ApplySchemaVendorExtensions>();
// In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique
// Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this
// works well because it prevents the "implementation detail" of type namespaces from leaking into your
// Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll
// need to opt out of this behavior to avoid Schema Id conflicts.
//
//c.UseFullTypeNameInSchemaIds();
// Alternatively, you can provide your own custom strategy for inferring SchemaId's for
// describing "complex" types in your API.
//
//c.SchemaId(t => t.FullName.Contains('`') ? t.FullName.Substring(0, t.FullName.IndexOf('`')) : t.FullName);
// Set this flag to omit schema property descriptions for any type properties decorated with the
// Obsolete attribute
//c.IgnoreObsoleteProperties();
// In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers.
// You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given
// enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different
// approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings.
//
//c.DescribeAllEnumsAsStrings();
// Similar to Schema filters, Swashbuckle also supports Operation and Document filters:
//
// Post-modify Operation descriptions once they've been generated by wiring up one or more
// Operation filters.
//
//c.OperationFilter<AddDefaultResponse>();
//
// If you've defined an OAuth2 flow as described above, you could use a custom filter
// to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required
// to execute the operation
//
//c.OperationFilter<AssignOAuth2SecurityRequirements>();
// Post-modify the entire Swagger document by wiring up one or more Document filters.
// This gives full control to modify the final SwaggerDocument. You should have a good understanding of
// the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md
// before using this option.
//
//c.DocumentFilter<ApplyDocumentVendorExtensions>();
// In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL
// to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions
// with the same path (sans query string) and HTTP method. You can workaround this by providing a
// custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs
//
//c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
// Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an
// alternative implementation for ISwaggerProvider with the CustomProvider option.
//
//c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
})
.EnableSwaggerUi(c =>
{
// Use the "InjectStylesheet" option to enrich the UI with one or more additional CSS stylesheets.
// The file must be included in your project as an "Embedded Resource", and then the resource's
// "Logical Name" is passed to the method as shown below.
//
//c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css");
// Use the "InjectJavaScript" option to invoke one or more custom JavaScripts after the swagger-ui
// has loaded. The file must be included in your project as an "Embedded Resource", and then the resource's
// "Logical Name" is passed to the method as shown above.
//
//c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js");
// The swagger-ui renders boolean data types as a dropdown. By default, it provides "true" and "false"
// strings as the possible choices. You can use this option to change these to something else,
// for example 0 and 1.
//
//c.BooleanValues(new[] { "0", "1" });
// By default, swagger-ui will validate specs against swagger.io's online validator and display the result
// in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the
// feature entirely.
//c.SetValidatorUrl("http://localhost/validator");
//c.DisableValidator();
// Use this option to control how the Operation listing is displayed.
// It can be set to "None" (default), "List" (shows operations for each resource),
// or "Full" (fully expanded: shows operations and their details).
//
//c.DocExpansion(DocExpansion.List);
// Specify which HTTP operations will have the 'Try it out!' option. An empty paramter list disables
// it for all operations.
//
//c.SupportedSubmitMethods("GET", "HEAD");
// Use the CustomAsset option to provide your own version of assets used in the swagger-ui.
// It's typically used to instruct Swashbuckle to return your version instead of the default
// when a request is made for "index.html". As with all custom content, the file must be included
// in your project as an "Embedded Resource", and then the resource's "Logical Name" is passed to
// the method as shown below.
//
//c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html");
// If your API has multiple versions and you've applied the MultipleApiVersions setting
// as described above, you can also enable a select box in the swagger-ui, that displays
// a discovery URL for each version. This provides a convenient way for users to browse documentation
// for different API versions.
//
//c.EnableDiscoveryUrlSelector();
// If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to
// the Swagger 2.0 specification, you can enable UI support as shown below.
//
//c.EnableOAuth2Support(
// clientId: "test-client-id",
// clientSecret: null,
// realm: "test-realm",
// appName: "Swagger UI"
// //additionalQueryStringParams: new Dictionary<string, string>() { { "foo", "bar" } }
//);
// If your API supports ApiKey, you can override the default values.
// "apiKeyIn" can either be "query" or "header"
//
//c.EnableApiKeySupport("apiKey", "header");
});
}
}
}

4 items are still needed:
Uncomment the line in the SwaggerConfig Register() method that says c.IncludeXmlComments(GetXmlCommentsPath());
Add a private static string property to your SwaggerConfig class that specifies the path for the XML
private static string GetXmlCommentsPath()
{
var path = string.Format(#"{0}bin\StarterAPI.XML", System.AppDomain.CurrentDomain.BaseDirectory);
return path;
}
Generate XML by changing your build options as follows: in Project Properties, Build tab, Output section CHECK the XML documentation file and make it read: bin\StarterAPI.xml Notice that it is the same path as generated in the previous step...this links the build XML creation to the SwaggerConfig consumer.
Lastly, annotate your controller methods with /// tags , [HttpGet] annotations and [Route("")] annotations. The sample below looks mostly complete:
// GET api/Stuff/17205
/// <summary>
/// Returns Stuff record based on supplied Stuff_Id
/// </summary>
/// <param name="stuffId"></param>
/// <returns></returns>
[HttpGet]
[Route("api/Stuff/{stuffId}")]
public Stuff_Request Get(int stuffId) {

With Swashbuckle version 5.6.0 (actually you probably need only Swashbuckle.Core nuget package) you can configure it very easy.
Create SwaggerConfig class that looks like this:
public static class SwaggerConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.EnableSwagger(Configure).EnableSwaggerUi(ConfigureUi);
}
static void Configure(SwaggerDocsConfig config)
{
config.SingleApiVersion("V1", "MichalBialecki.com.Swagger.Example");
config.UseFullTypeNameInSchemaIds();
}
static void ConfigureUi(SwaggerUiConfig config)
{
config.DocExpansion(DocExpansion.None);
config.DisableValidator();
}
}
You also need to register it in Startup.cs:
SwaggerConfig.Register(config);
And that's it. It works for me.

Related

Apollo conditional data sources & initialization lifecycle

I have a specific use case where a user’s data sources are conditional - e.g based on the data sources saved in the database for every specific user.
This also means every data source has unique credentials for every user, which is fine for RESTDataSource because I can use the willSendRequest to set the Authentication headers before each request.
However, I have custom data sources that have proprietary clients (for example JSForce for Salesforce) - and they have their own fetch mechanism.
As of now - I have a custom transformer directive that fetches the tokens from the database and adds it into the context - however, the directive is ran before the dataSource.initialize() method - so that I can’t use the credentials there because the context still doesn’t have it.
I also don’t want to initialize all data sources for every user even if he doesn’t use said data source in this request - but the dataSources() function doesn’t accept any parameter and is not contextual.
Bottom line is - is it possible to pass data sources conditionally based even on the Express request? When is the right time to pass the tokens and credentials to the dataSource? Maybe add my own custom init function and call it from the directive?
So you have options. Here are 2 choices:
1. Just add your dataSources
If you just initialize all dataSources, internally it can check to see if the user has access. You could have a getClient function that resolves on the client or throws an UnauthorizedError, depending.
2. Don't just add your dataSources
So if you really don't want to initialize the dataSources at ALL, you can absolutely do this by adding the "dataSources" yourself, just like Apollo does it.
const server = new ApolloServer({
// this example uses apollo-server-express
context: async ({ req, res }) => {
const accessToken = req.headers?.authorization?.split(' ')[1] || ''
const user = accessToken && buildUser(accessToken)
const context = { user }
// You can't use the name "dataSources" in your config because ApolloServer will puke, so I called them "services"
await addServices(context)
return context
}
})
const addServices = async (context) => {
const { user } = context;
const services = {
userAPI: new UserAPI(),
postAPI: new PostAPI(),
}
if (user.isAdmin) {
services.adminAPI = new AdminAPI()
}
const initializers = [];
for (const service of Object.values(services)) {
if (service.initialize) {
initializers.push(
service.initialize({
context,
cache: null, // or add your own cache
})
);
}
}
await Promise.all(initializers);
/**
* this is where you have to deviate from Apollo.
* You can't use the name "dataSources" in your config because ApolloServer will puke
* with the error 'Please use the dataSources config option instead of putting dataSources on the context yourself.'
*/
context.services = services;
}
Some notes:
1. You can't call them "dataSources"
If you return a property called "dataSources" on your context object, Apollo will not like it very much [meaning it throws an Error]. In my example, I used the name "services", but you can do whatever you want... except "dataSources".
With the above code, in your resolvers, just reference context.services.whatever instead.
2. This is what Apollo does
This pattern is copied directly from what Apollo already does for dataSources [source]
3. I recommend you still treat them as DataSources
I recommend you stick to the DataSources pattern and that your "services" all extend DataSource. It's going to be easier for everyone involved.
4. Type safety
If you're using TypeScript or something, you're going to lose a bit of type safety, since the context.services is either going to be one shape or another. Even if you're not, if you're not careful, you may end up throwing "Cannot read property users of undefined" errors instead of "Unauthorized" errors. You might be better off creating "dummy services" that reflect the same object shape but just throw Unauthorized.

How to configure Filter with specific message type?

I want to consume messages only with specific type and properties set. A sort of message content filter before any consumer instance created.
I'm trying to create a filter for specific ConsumeContext:
public class OrderFilter : IFilter<ConsumeContext<CreateOrderMessage>>
{
public Task Send(ConsumeContext<CreateOrderMessage> context, IPipe<ConsumeContext<CreateOrderMessage>> next)
{
if (context.Message.IsTrustedUser)
{
return next.Send(context); // continue processing
}
return Task.CompletedTask; // stop message processing
}
public void Probe(ProbeContext context) { }
}
How can I register such a filter?
I've tried to register it in the endpoint but with no luck. I have
cfg.ReceiveEndpoint("OrderQueue", ep =>
{
ep.UseFilter(new OrderFilter());
ep.Consumer<CreateOrderConsumer>();
});
I have the following error: Cannot convert instance argument type '{MassTransit.IReceiveEndpointConfigurator,MassTransit.RabbitMqTransport.IRabbitMqReceiveEndpointConfigurator}' to 'GreenPipes.IPipeConfigurator<MassTransit.ConsumeContext<Core.CreateOrderMessage>>'
So, there used to be an extension method for this purpose, but I can't find it. You can add the filter prior to the consumer being created by creating a filter specification and adding it as shown below.
var filter = new OrderFilter();
var specification = new FilterPipeSpecification<ConsumeContext< CreateOrderMessage >>(filter);
ep.AddPipeSpecification(specification);
If you want to execute the filter after the consumer has been created (for instance, if you're using container scope to share information), you can use a scope consume filter (which is described in several answers, as well as the documentation) or you can add your filter during consumer configuration.
ep.Consumer<CreateOrderConsumer>(cc =>
{
cc.Message<CreateOrderMessage>(mc => mc.UseFilter(new OrderFilter()));
}

Microsoft.OData.Client $expand does not populate the model

I am using Microsoft.OData.Client based on the microsoft sample application.
Here's my simple WebAPI Controller:
[Route("test")]
[HttpGet]
public IHttpActionResult Test()
{
var context = _dynamicsContextFactory.CreateContext();
// adding this had no effect // context.MergeOption = MergeOption.AppendOnly;
// adding this had no effect // context.MergeOption = MergeOption.OverwriteChanges;
// adding this had no effect // context.MergeOption = MergeOption.NoTracking;
// adding this had no effect // context.MergeOption = MergeOption.PreserveChanges;
var result = context.SalesOrderHeadersV2.Expand("SalesOrderLines").Take(1).ToList();
return Ok(result);
}
The client generates the correct URL.
https://example.com/data/SalesOrderHeadersV2?$top=1&$expand=SalesOrderLines
I can see in fiddler the SalesOrderLines property returned in the JSON.
However when I inspect the result variable (or view the output) there is no SalesOrderLines property. So the order lines have not been mapped into my result object from the data downloaded from the oData source.
Important Note: I am using EDMXTrimmer to reduce the number of entities in my client, could this be an issue If I’m missing a joining entity? (It seems unlikely there's a joining entity in this case)
Clue?
When I try to change this line:
var result = context.SalesOrderHeadersV2.Expand(x=>x.SalesOrderLines).Take(1).ToList();
It will not compile because 'SalesOrderHeaderV2' does not contain a definition for 'SalesOrderLines' ...
Note: context.SalesOrderLines does exist.
The issue was that EDMXTrimmer removed the navigation properties.
EDMXTrimmer has since been fixed.

Localization with SharedResources not working in .NET Core 2.1

I've been fighting with this problem for hours... and I can't find what it is...
I'm just trying to localize the _Layout.cshtml file. Both the IStringLocalizer and the IHtmlLocalizer do not seem to find the Resource files.
I've followed and searched for:
https://github.com/MormonJesus69420/SharedResourcesExample
.Net Core Data Annotations - localization with shared resources
https://stackoverflow.com/search?q=shared+resources+.net+core
https://andrewlock.net/adding-localisation-to-an-asp-net-core-application/
There's something silly that I may be overlooking.
Here's my startup.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using EduPlaTools.Data;
using EduPlaTools.Models;
using EduPlaTools.Services;
using System.Globalization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Pomelo.EntityFrameworkCore.MySql;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Microsoft.AspNetCore.HttpOverrides;
namespace EduPlaTools
{
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)
{
// This is for string translation!
// Adds Localization Services (StringLocalizer, HtmlLocalizer, etc.)
// the opts.ResourcesPath = is the path in which the resources are found.
// In our case the folder is named Resources!
// There's specific and neutral resources. (Specific en-US). (Neutral: es)
/**
* If no ResourcesPath is specified, the view's resources will be expected to be next to the views.
* If ResourcesPath were set to "resources", then view resources would be expected to be ina Resource directory,
* in a path speicifc to their veiw (Resources/Views/Home/About.en.resx, for example).
*
* */
services.AddLocalization(opts => opts.ResourcesPath = "Resources");
// services.AddBContext
// There are subtle differences between the original and the modified version.
services.AddDbContextPool<ApplicationDbContext>(options =>
options.UseMySql(Configuration.GetConnectionString("MySQLConnection"),
mysqlOptions =>
{
mysqlOptions.ServerVersion(new Version(8, 0, 12), ServerType.MySql); // replace with your Server Version and Type
}
));
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => options.ResourcesPath = "Resources")
.AddDataAnnotationsLocalization();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This may be dangerous and is not recommended
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
.Database.Migrate();
}
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// These must line up with the ending of the .resx files.
// Example: SharedResources.en.resx, SharedResources.es.rex
// If you want to add specific, then do it like:
// new CultureInfo("en-US")
List<CultureInfo> supportedCultures = new List<CultureInfo>
{
new CultureInfo("es"),
new CultureInfo("en"),
new CultureInfo("es-ES"),
new CultureInfo("en-US")
};
// Registers the localization, and changes the localization per request.
app.UseRequestLocalization(new RequestLocalizationOptions
{
// We give the default support of Spanish.
DefaultRequestCulture = new RequestCulture("es"),
// Format numbers, dates, etc.
SupportedCultures = supportedCultures,
// The strings that we have localized
SupportedUICultures = supportedCultures
});
// This will seed the databse:
SeedDatabase.Initialize(app.ApplicationServices);
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Here's how I'm trying to call it inside the _Layout.cshtml:
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
#inject IStringLocalizer<SharedResources> SharedLocalizer
#inject IHtmlLocalizer<SharedResources> _localizer;
#SharedLocalizer["Menu_Home"]
Here's the directory structure:
Here are the contents of SharedResources.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace EduPlaTools
{
/**
* This is a dummy class that is needed so Localization works.
* Now in .NET Core Localization works as a service, and implementsw
* naming conventions (AT the file level). Therefore, if the files do not
* implement the correct name, there's going to be problems.
*
* See an example, here:
* https://github.com/SteinTheRuler/ASP.NET-Core-Localization/blob/master/Resources/SharedResources.cs
*
* This is a workaround to create a Resource File that can be read by the entire
* application. It's left in blank so the convention over configuration
* picks it up.
*
* */
public class SharedResources
{
}
}
Here are the contents of the resx files:
I've also tried renaming them to no avail.. (Tried Resources.es.rex, Resources.rex)
I tried setting breakpoints to see how it behaved. It of course, didn't find the Resource files. I then compared it with Mormon's repo by recalling an inexistent key. I compared it with my output, but Mormon's repo doesn't display the "SearchedLocation" (Was it introduced in a later .NET Core version?)
Mormon's Repo:
My repo:
I know this may be something silly... But it's been close to 4 hours, and I can't stop since I have a LOT to do!!
Any ideas?
if you want to implement localization with shared resource, you have to create your own culture localizer class:
public class CultureLocalizer
{
private readonly IStringLocalizer _localizer;
public CultureLocalizer(IStringLocalizerFactory factory)
{
var type = typeof(ViewResource);
var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
_localizer = factory.Create("ViewResource", assemblyName.Name);
}
// if we have formatted string we can provide arguments
// e.g.: #Localizer.Text("Hello {0}", User.Name)
public LocalizedString Text(string key, params string[] arguments)
{
return arguments == null
? _localizer[key]
: _localizer[key, arguments];
}
}
then register it is startup:
services.AddSingleton<CultureLocalizer>();
and modify view locaization settings :
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(o=>o.ResourcesPath = "Resources")
in your views you have to inject the culture localizer class before using it.
those are initial settings for view localization with shared resource, you need to configure localization settings for DataAnnotation, ModelBinding and Identity error messages as well.
these articles could help for starting:
Developing multicultural web application with ASP.NET Core 2.1 Razor Pages:
http://www.ziyad.info/en/articles/10-Developing_Multicultural_Web_Application
it includes step by step tutorial for localizing using shared resources, additionally, this article is about localizing Identity error messages :
http://ziyad.info/en/articles/20-Localizing_Identity_Error_Messages
I wanted to add an answer which further develops Laz's solution. Just in case someone wants to have individual localized views.
Back in Startup.cs, you have:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(o=>o.ResourcesPath = "Resources")
Technically, you are indicating MVC to look in the "Resources" folder as the main path, and then follow the convention to look for localized resource files.
Therefore
In case you want to localize the Login.cshtml view found in Views/Account/Login.chsmtl, you have to create the resource file in: Resources/Views/Account/Login.en.resx
You would then need to add the following either in the view directly Login.cshtml or in the _ViewImports.cshtml to reference it to all the views:
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
After that, in your code you can do:
Localizer["My_Resource_file_key"]
And you'll have it translated.
Here are some illustrations:
An update to the previous answers. Due to the recent breaking change in .NET Core 3 (https://github.com/dotnet/docs/issues/16964), the accepted answer will only work if the resource lives directly in the resource folder.
I have created a workaround to use shared resources in views (same applies to controllers, data annotations, services, whatever you need...).
First you need to create an empty class for your resources. This one has to live under YourApp.Resources namespace. then create your resources named same as your class (in my example I have Views.cs in the namespace MyApp.Resources.Shared and Views.resx).
Then here is the helper class to load the shared resources:
public class SharedViewLocalizer
{
private readonly IStringLocalizer _localizer;
public SharedViewLocalizer(IStringLocalizerFactory factory)
{
var assemblyName = new AssemblyName(typeof(Resources.Shared.Views).GetTypeInfo().Assembly.FullName);
localizer = factory.Create("Shared.Views", assemblyName.Name);
}
public string this[string key] => _localizer[key];
public string this[string key, params object[] arguments] => _localizer[key, arguments];
}
You have to register is in the Startup.Configure:
services.AddSingleton<SharedViewLocalizer>();
I suppose you use
services.AddLocalization(options => options.ResourcesPath = "Resources");
to setup default resources location.
And then in your view you use it as follows:
#inject IViewLocalizer _localizer
#inject SharedViewLocalizer _sharedLocalizer
#_localizer["View spacific resource"] // Resource from Resources/Views/ControllerName/ViewName.resx
#_sharedLocalizer["Shared resource"] // Resource from Resources/Shared/Views.resx
#_sharedLocalizer["Also supports {0} number of arguments", "unlimited"]
Same principle can be applied to DataAnnotations where we can use the built-in method in Startup.Configure:
services.AddMvc()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
var assemblyName = new AssemblyName(typeof(DataAnnotations).GetTypeInfo().Assembly.FullName);
return factory.Create("Shared.DataAnnotations", assemblyName.Name
};
})
.AddViewLocalization();
Again, I'm expecting my resources to live in the namespace Resources.Shared and have an empty class called DataAnnotations created.
Hope this helps to overcome the current breaking change problems.

use camel case serialization only for specific actions

I've used WebAPI for a while, and generally set it to use camel case json serialization, which is now rather common and well documented everywhere.
Recently however, working on a much larger project, I came across a more specific requirement: we need to use camel case json serialization, but because of backward compatibility issues with our client scripts, I only want it to happen for specific actions, to avoid breaking other parts of the (extremely large) website.
I figure one option is to have a custom content type, but that then requires client code to specify it.
Is there any other option?
Thanks!
Try this:
public class CamelCasingFilterAttribute : ActionFilterAttribute
{
private JsonMediaTypeFormatter _camelCasingFormatter = new JsonMediaTypeFormatter();
public CamelCasingFilterAttribute()
{
_camelCasingFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ObjectContent content = actionExecutedContext.Response.Content as ObjectContent;
if (content != null)
{
if (content.Formatter is JsonMediaTypeFormatter)
{
actionExecutedContext.Response.Content = new ObjectContent(content.ObjectType, content.Value, _camelCasingFormatter);
}
}
}
}
Apply this [CamelCasingFilter] attribute to any action you want to camel-case. It will take any JSON response you were about to send back and convert it to use camel casing for the property names instead.

Resources